diff --git a/README.md b/README.md index d4ba7c9ef..967654c56 100644 --- a/README.md +++ b/README.md @@ -9,16 +9,26 @@ I need an app that uses MVVM Light, uses master detail, can suspend and resume, ## Build Status -|Branch |CI |Gen Tests |Full Tests |Test Version|Version| -|:--------|:----------------:|:---------------:|:---------------:|:---------------:|:---------------:| -|master|[![Build status](https://ci.appveyor.com/api/projects/status/nf8r35r45o4yqbqs/branch/master?svg=true)](https://ci.appveyor.com/project/ralarcon/windowstemplatestudio/branch/master)|[![Generation Tests](https://winappstudio.visualstudio.com/_apis/public/build/definitions/5c80cfe7-3bfb-4799-9d04-803c84df7a60/141/badge)](https://winappstudio.visualstudio.com/DefaultCollection/Vegas/_build/index?definitionId=141)|[![Full Integration Tests](https://winappstudio.visualstudio.com/_apis/public/build/definitions/5c80cfe7-3bfb-4799-9d04-803c84df7a60/129/badge)](https://winappstudio.visualstudio.com/DefaultCollection/Vegas/_build/index?definitionId=129)|[![Prerelease Version](https://wtsrepository.blob.core.windows.net/badges/img.prerelease.version.svg)](https://github.com/Microsoft/WindowsTemplateStudio/blob/master/docs/getting-started-extension.md#nightly--pre-release-feeds-for-windows-template-studio) |[![Production Version](https://wtsrepository.blob.core.windows.net/badges/img.release.version.svg)](https://marketplace.visualstudio.com/items?itemName=WASTeamAccount.WindowsTemplateStudio)| -|dev|[![Build status](https://ci.appveyor.com/api/projects/status/nf8r35r45o4yqbqs/branch/dev?svg=true)](https://ci.appveyor.com/project/ralarcon/windowstemplatestudio/branch/dev)|[![Generation Tests](https://winappstudio.visualstudio.com/_apis/public/build/definitions/5c80cfe7-3bfb-4799-9d04-803c84df7a60/135/badge)](https://winappstudio.visualstudio.com/DefaultCollection/Vegas/_build/index?definitionId=135)|[![Full Integration Tests](https://winappstudio.visualstudio.com/_apis/public/build/definitions/5c80cfe7-3bfb-4799-9d04-803c84df7a60/128/badge)](https://winappstudio.visualstudio.com/DefaultCollection/Vegas/_build/index?definitionId=128)|[![Nightly Version](https://wtsrepository.blob.core.windows.net/badges/img.nightly.version.svg)](https://github.com/Microsoft/WindowsTemplateStudio/blob/master/docs/getting-started-extension.md#nightly--pre-release-feeds-for-windows-template-studio)|| + +|Branch |CI |Test Version|Version| +|:--------|:----------------:|:---------------:|:---------------:| +|master|[![Build status](https://ci.appveyor.com/api/projects/status/nf8r35r45o4yqbqs/branch/master?svg=true)](https://ci.appveyor.com/project/ralarcon/windowstemplatestudio/branch/master)|[![Prerelease Version](https://wtsrepository.blob.core.windows.net/badges/img.prerelease.version.svg)](https://github.com/Microsoft/WindowsTemplateStudio/blob/master/docs/getting-started-extension.md#nightly--pre-release-feeds-for-windows-template-studio) |[![Production Version](https://wtsrepository.blob.core.windows.net/badges/img.release.version.svg)](https://marketplace.visualstudio.com/items?itemName=WASTeamAccount.WindowsTemplateStudio)| +|dev|[![Build status](https://ci.appveyor.com/api/projects/status/nf8r35r45o4yqbqs/branch/dev?svg=true)](https://ci.appveyor.com/project/ralarcon/windowstemplatestudio/branch/dev)|[![Nightly Version](https://wtsrepository.blob.core.windows.net/badges/img.nightly.version.svg)](https://github.com/Microsoft/WindowsTemplateStudio/blob/master/docs/getting-started-extension.md#nightly--pre-release-feeds-for-windows-template-studio)|| + + + +|Branch |Gen Tests |Full Tests |WACK Tests | +|:--------|:---------------:|:---------------:|:---------------:| +|master|[![Generation Tests](https://winappstudio.visualstudio.com/_apis/public/build/definitions/5c80cfe7-3bfb-4799-9d04-803c84df7a60/141/badge)](https://winappstudio.visualstudio.com/DefaultCollection/Vegas/_build/index?definitionId=141)|[![Full Integration Tests](https://winappstudio.visualstudio.com/_apis/public/build/definitions/5c80cfe7-3bfb-4799-9d04-803c84df7a60/129/badge)](https://winappstudio.visualstudio.com/DefaultCollection/Vegas/_build/index?definitionId=129)| +|dev|[![Generation Tests](https://winappstudio.visualstudio.com/_apis/public/build/definitions/5c80cfe7-3bfb-4799-9d04-803c84df7a60/135/badge)](https://winappstudio.visualstudio.com/DefaultCollection/Vegas/_build/index?definitionId=135)|[![Full Integration Tests](https://winappstudio.visualstudio.com/_apis/public/build/definitions/5c80cfe7-3bfb-4799-9d04-803c84df7a60/128/badge)](https://winappstudio.visualstudio.com/DefaultCollection/Vegas/_build/index?definitionId=128)|[![Wack Tests](https://winappstudio.visualstudio.com/DefaultCollection/_apis/public/build/definitions/5c80cfe7-3bfb-4799-9d04-803c84df7a60/142/badge)](https://winappstudio.visualstudio.com/DefaultCollection/Vegas/_build/index?definitionId=142) + + > The builds include test verifications to validate the contributions: > * *CI Build*: Includes all unit test + minimum integration verifications (minumum generation + build + code style rules). Runs every PR requested / PR accepted. > * *Gen Tests*: Includes tests to verify combinations and variations of templates from a project generation point of view. Runs every PR accepted and takes a bit to complete. > * *Full Tests*: Includes `Gen Tests` and actually builds the solutions generated to ensure no build time issues found. Runs every PR accepted and takes longer to be completed. -> +> * *Wack Tests*: Includes tests that run the App Certification Kit against the generated projects to ensure there are no issues blocking a submission to the store. Runs once nightly and takes quite a while to complete. ## Features diff --git a/code/src/Core/Core.csproj b/code/src/Core/Core.csproj index 4b3066ed3..ac9a7d189 100644 --- a/code/src/Core/Core.csproj +++ b/code/src/Core/Core.csproj @@ -182,6 +182,8 @@ + + diff --git a/code/src/Core/PostActions/Catalog/Merge/GetMergeFilesFromProjectPostAction.cs b/code/src/Core/PostActions/Catalog/Merge/GetMergeFilesFromProjectPostAction.cs index cf64be85c..4d85f196f 100644 --- a/code/src/Core/PostActions/Catalog/Merge/GetMergeFilesFromProjectPostAction.cs +++ b/code/src/Core/PostActions/Catalog/Merge/GetMergeFilesFromProjectPostAction.cs @@ -18,7 +18,7 @@ namespace Microsoft.Templates.Core.PostActions.Catalog.Merge public override void Execute() { - if (Regex.IsMatch(_config, MergePostAction.GlobalExtension)) + if (Regex.IsMatch(_config, MergeConfiguration.GlobalExtension)) { GetFileFromProject(); } @@ -55,16 +55,16 @@ namespace Microsoft.Templates.Core.PostActions.Catalog.Merge private string GetMergeFileFromDirectory(string directory) { - if (Path.GetFileName(_config).StartsWith(MergePostAction.Extension)) + if (Path.GetFileName(_config).StartsWith(MergeConfiguration.Extension)) { var extension = Path.GetExtension(_config); - return Directory.EnumerateFiles(directory, $"*{extension}").FirstOrDefault(f => !Regex.IsMatch(f, MergePostAction.PostactionRegex)); + return Directory.EnumerateFiles(directory, $"*{extension}").FirstOrDefault(f => !Regex.IsMatch(f, MergeConfiguration.PostactionRegex)); } else { var filePath = Path.Combine(directory, Path.GetFileName(_config)); - var path = Regex.Replace(filePath, MergePostAction.PostactionRegex, "."); + var path = Regex.Replace(filePath, MergeConfiguration.PostactionRegex, "."); return path; } diff --git a/code/src/Core/PostActions/Catalog/Merge/MergeConfiguration.cs b/code/src/Core/PostActions/Catalog/Merge/MergeConfiguration.cs index 9ae891897..40c808e69 100644 --- a/code/src/Core/PostActions/Catalog/Merge/MergeConfiguration.cs +++ b/code/src/Core/PostActions/Catalog/Merge/MergeConfiguration.cs @@ -12,6 +12,19 @@ namespace Microsoft.Templates.Core.PostActions.Catalog.Merge { public class MergeConfiguration { + public const string Suffix = "postaction"; + public const string NewSuffix = "failedpostaction"; + + public const string PostactionRegex = @"(\$\S*)?(_" + Suffix + "|_g" + Suffix + @")\."; + public const string FailedPostactionRegex = @"(\$\S*)?(_" + NewSuffix + "|_g" + NewSuffix + @")(\d)?\."; + + public const string Extension = "_" + Suffix + "."; + public const string GlobalExtension = "$*_g" + Suffix + "."; + + public const string ResourceDictionaryMatch = @""; + public string FilePath { get; private set; } public bool FailOnError { get; private set; } diff --git a/code/src/Core/PostActions/Catalog/Merge/MergeFailureType.cs b/code/src/Core/PostActions/Catalog/Merge/MergeFailureType.cs index 958e49e1a..29ee92ed4 100644 --- a/code/src/Core/PostActions/Catalog/Merge/MergeFailureType.cs +++ b/code/src/Core/PostActions/Catalog/Merge/MergeFailureType.cs @@ -7,6 +7,7 @@ namespace Microsoft.Templates.Core.PostActions.Catalog.Merge public enum MergeFailureType { FileNotFound, - LineNotFound + LineNotFound, + KeyAlreadyDefined } } diff --git a/code/src/Core/PostActions/Catalog/Merge/MergePostAction.cs b/code/src/Core/PostActions/Catalog/Merge/MergePostAction.cs index 2c65613b9..55b6f9bdd 100644 --- a/code/src/Core/PostActions/Catalog/Merge/MergePostAction.cs +++ b/code/src/Core/PostActions/Catalog/Merge/MergePostAction.cs @@ -16,15 +16,6 @@ namespace Microsoft.Templates.Core.PostActions.Catalog.Merge { public class MergePostAction : PostAction { - private const string Suffix = "postaction"; - private const string NewSuffix = "failedpostaction"; - - public const string Extension = "_" + Suffix + "."; - public const string GlobalExtension = "$*_g" + Suffix + "."; - - public const string PostactionRegex = @"(\$\S*)?(_" + Suffix + "|_g" + Suffix + @")\."; - public const string FailedPostactionRegex = @"(\$\S*)?(_" + NewSuffix + "|_g" + NewSuffix + @")(\d)?\."; - public MergePostAction(MergeConfiguration config) : base(config) { } @@ -80,32 +71,36 @@ namespace Microsoft.Templates.Core.PostActions.Catalog.Merge File.Delete(_config.FilePath); } - private void AddFailedMergePostActionsFileNotFound(string originalFilePath) + protected void AddFailedMergePostActions(string originalFilePath, MergeFailureType mergeFailureType, string description) { - var sourceFileName = originalFilePath.Replace(GenContext.Current.OutputPath + Path.DirectorySeparatorChar, string.Empty); - var postactionFileName = _config.FilePath.Replace(GenContext.Current.OutputPath + Path.DirectorySeparatorChar, string.Empty); - - var description = string.Format(StringRes.FailedMergePostActionFileNotFound, sourceFileName); + var sourceFileName = GetRelativePath(originalFilePath); + var postactionFileName = GetRelativePath(_config.FilePath); var failedFileName = GetFailedPostActionFileName(); - GenContext.Current.FailedMergePostActions.Add(new FailedMergePostAction(sourceFileName, _config.FilePath, failedFileName, description, MergeFailureType.FileNotFound)); + GenContext.Current.FailedMergePostActions.Add(new FailedMergePostAction(sourceFileName, _config.FilePath, failedFileName, description, mergeFailureType)); File.Copy(_config.FilePath, failedFileName, true); } + protected string GetRelativePath(string path) + { + return path.Replace(GenContext.Current.OutputPath + Path.DirectorySeparatorChar, string.Empty); + } + + private void AddFailedMergePostActionsFileNotFound(string originalFilePath) + { + var description = string.Format(StringRes.FailedMergePostActionFileNotFound, GetRelativePath(originalFilePath)); + AddFailedMergePostActions(originalFilePath, MergeFailureType.FileNotFound, description); + } + private void AddFailedMergePostActionsAddLineNotFound(string originalFilePath, string errorLine) { - var sourceFileName = originalFilePath.Replace(GenContext.Current.OutputPath + Path.DirectorySeparatorChar, string.Empty); - - var postactionFileName = _config.FilePath.Replace(GenContext.Current.OutputPath + Path.DirectorySeparatorChar, string.Empty); - var description = string.Format(StringRes.FailedMergePostActionLineNotFound, errorLine.Trim(), sourceFileName); - var failedFileName = GetFailedPostActionFileName(); - GenContext.Current.FailedMergePostActions.Add(new FailedMergePostAction(sourceFileName, _config.FilePath, failedFileName, description, MergeFailureType.LineNotFound)); - File.Copy(_config.FilePath, failedFileName, true); + var description = string.Format(StringRes.FailedMergePostActionLineNotFound, errorLine.Trim(), GetRelativePath(originalFilePath)); + AddFailedMergePostActions(originalFilePath, MergeFailureType.LineNotFound, description); } private string GetFailedPostActionFileName() { - var newFileName = Path.GetFileNameWithoutExtension(_config.FilePath).Replace(Suffix, NewSuffix); + var newFileName = Path.GetFileNameWithoutExtension(_config.FilePath).Replace(MergeConfiguration.Suffix, MergeConfiguration.NewSuffix); var folder = Path.GetDirectoryName(_config.FilePath); var extension = Path.GetExtension(_config.FilePath); @@ -120,16 +115,16 @@ namespace Microsoft.Templates.Core.PostActions.Catalog.Merge private string GetFilePath() { - if (Path.GetFileName(_config.FilePath).StartsWith(Extension, StringComparison.InvariantCultureIgnoreCase)) + if (Path.GetFileName(_config.FilePath).StartsWith(MergeConfiguration.Extension, StringComparison.InvariantCultureIgnoreCase)) { var extension = Path.GetExtension(_config.FilePath); var directory = Path.GetDirectoryName(_config.FilePath); - return Directory.EnumerateFiles(directory, $"*{extension}").FirstOrDefault(f => !f.Contains(Suffix)); + return Directory.EnumerateFiles(directory, $"*{extension}").FirstOrDefault(f => !f.Contains(MergeConfiguration.Suffix)); } else { - var path = Regex.Replace(_config.FilePath, PostactionRegex, "."); + var path = Regex.Replace(_config.FilePath, MergeConfiguration.PostactionRegex, "."); return path; } diff --git a/code/src/Core/PostActions/Catalog/Merge/MergeResourceDictionaryPostAction.cs b/code/src/Core/PostActions/Catalog/Merge/MergeResourceDictionaryPostAction.cs new file mode 100644 index 000000000..2d7f7b234 --- /dev/null +++ b/code/src/Core/PostActions/Catalog/Merge/MergeResourceDictionaryPostAction.cs @@ -0,0 +1,117 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Xml; +using System.Xml.Linq; +using Microsoft.Templates.Core.Gen; +using Microsoft.Templates.Core.Resources; + +namespace Microsoft.Templates.Core.PostActions.Catalog.Merge +{ + public class MergeResourceDictionaryPostAction : MergePostAction + { + private const string mergeDictionaryPattern = @" + + + + +"; + + public MergeResourceDictionaryPostAction(MergeConfiguration config) : base(config) + { + } + + public override void Execute() + { + string originalFilePath = GetFilePath(); + if (!File.Exists(originalFilePath)) + { + File.Copy(_config.FilePath, originalFilePath); + GenContext.Current.ProjectItems.Add(originalFilePath); + AddToMergeDictionary(originalFilePath); + } + else + { + var mergeRoot = XElement.Load(_config.FilePath); + var sourceRoot = XElement.Load(originalFilePath); + + foreach (var node in GetNodesToMerge(mergeRoot)) + { + var sourceNode = sourceRoot.Elements().FirstOrDefault(e => GetKey(e) == GetKey(node)); + if (sourceNode == null) + { + AddNodeToSource(sourceRoot, node); + } + else + { + if (!XNode.DeepEquals(node, sourceNode)) + { + var errorMessage = string.Format(StringRes.FailedMergePostActionKeyAlreadyDefined, GetKey(node), GetRelativePath(originalFilePath)); + if (_config.FailOnError) + { + throw new InvalidDataException(errorMessage); + } + else + { + AddFailedMergePostActions(originalFilePath, MergeFailureType.KeyAlreadyDefined, errorMessage); + File.Delete(_config.FilePath); + return; + } + } + } + } + + using (TextWriter writeFile = new StreamWriter(originalFilePath)) + { + var writer = new ResourceDictionaryWriter(writeFile); + writer.WriteResourceDictionary(sourceRoot); + writer.Flush(); + writer.Close(); + } + } + + File.Delete(_config.FilePath); + } + + private static void AddToMergeDictionary(string originalFilePath) + { + var relPath = originalFilePath.Replace(GenContext.Current.OutputPath, "").Replace(@"\", @"/"); + var postactionContent = mergeDictionaryPattern.Replace("{filePath}", relPath); + var mergeDictionaryName = Path.GetFileNameWithoutExtension(originalFilePath); + File.WriteAllText(GenContext.Current.OutputPath + $"/App${mergeDictionaryName}_gpostaction.xaml", postactionContent); + } + + private static void AddNodeToSource(XElement sourceRoot, XElement node) + { + if (node.PreviousNode != null && node.PreviousNode.NodeType == XmlNodeType.Comment) + { + sourceRoot.Add(node.PreviousNode); + } + + sourceRoot.Add(node); + } + + private string GetKey(XElement node) + { + XNamespace ns = node.GetNamespaceOfPrefix("x"); + return node.Attribute(ns + "Key").Value; + } + + private IEnumerable GetNodesToMerge(XElement rootNode) + { + return rootNode.Descendants().Where(e => e.Attributes().Any(a => a.Name.LocalName == "Key")); + } + + private string GetFilePath() + { + return Regex.Replace(_config.FilePath, MergeConfiguration.PostactionRegex, "."); + } + } +} diff --git a/code/src/Core/PostActions/Catalog/Merge/ResourceDictionaryWriter.cs b/code/src/Core/PostActions/Catalog/Merge/ResourceDictionaryWriter.cs new file mode 100644 index 000000000..fc4e10788 --- /dev/null +++ b/code/src/Core/PostActions/Catalog/Merge/ResourceDictionaryWriter.cs @@ -0,0 +1,133 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.IO; +using System.Linq; +using System.Text; +using System.Xml; +using System.Xml.Linq; + +namespace Microsoft.Templates.Core.PostActions.Catalog.Merge +{ + public class ResourceDictionaryWriter : XmlTextWriter + { + private TextWriter writer; + private const string intend = " "; + + public ResourceDictionaryWriter(TextWriter w) : base(w) + { + writer = w; + } + + public ResourceDictionaryWriter(Stream w, Encoding encoding) : base(w, encoding) + { + } + + public ResourceDictionaryWriter(string filename, Encoding encoding) : base(filename, encoding) + { + } + + public override void WriteStartElement(string prefix, string localName, string ns) + { + if (!string.IsNullOrEmpty(prefix)) + { + localName = prefix + ":" + localName; + prefix = ""; + } + + base.WriteStartElement(prefix, localName, ns); + } + + public new void WriteAttributeString(string prefix, string localName, string value) + { + if (!string.IsNullOrEmpty(prefix)) + { + localName = prefix + ":" + localName; + } + + WriteAttributeString(localName, value); + } + + public void WriteResourceDictionary(XElement e) + { + WriteStartElement(e.Name.LocalName); + WriteNamespaceDeclaration("xmlns", "http://schemas.microsoft.com/winfx/2006/xaml/presentation"); + WriteNamespaceDeclaration("xmlns:x", "http://schemas.microsoft.com/winfx/2006/xaml"); + WriteNewLine(); + WriteNewLine(); + foreach (var node in e.Elements()) + { + WriteElement(node); + } + WriteFullEndElement(); + WriteNewLine(); + } + + private void WriteElement(XElement e) + { + WriteComments(e); + WriteRaw(intend); + WriteStartElement(e.GetPrefixOfNamespace(e.Name.Namespace), e.Name.LocalName, ""); + WriteAttributes(e); + if (e.Descendants().Count() > 0) + { + WriteChildElements(e); + } + else + { + WriteRaw(e.Value); + } + WriteEndElement(); + WriteNewLine(); + if (e.Name.LocalName == "Style") + { + WriteNewLine(); + } + } + + private void WriteNewLine() + { + WriteRaw("\r\n"); + } + + private void WriteNamespaceDeclaration(string key, string value) + { + writer.WriteLine(); + WriteAttributeString(" " + key, value); + } + + private void WriteComments(XElement e) + { + if (e.PreviousNode != null && e.PreviousNode.NodeType == XmlNodeType.Comment) + { + WriteRaw(intend); + WriteComment((e.PreviousNode as XComment).Value); + WriteNewLine(); + } + } + + private void WriteChildElements(XElement e) + { + WriteNewLine(); + foreach (var n in e.Descendants()) + { + WriteRaw(intend); + WriteRaw(intend); + WriteStartElement(e.GetPrefixOfNamespace(n.Name.Namespace), n.Name.LocalName, ""); + WriteAttributes(n); + WriteEndElement(); + WriteNewLine(); + } + WriteRaw(intend); + } + + private void WriteAttributes(XElement e) + { + foreach (var a in e.Attributes()) + { + WriteAttributeString(e.GetPrefixOfNamespace(a.Name.Namespace), a.Name.LocalName, a.Value); + } + } + } +} diff --git a/code/src/Core/PostActions/NewItemPostActionFactory.cs b/code/src/Core/PostActions/NewItemPostActionFactory.cs index 770ecc3ec..1eea29a34 100644 --- a/code/src/Core/PostActions/NewItemPostActionFactory.cs +++ b/code/src/Core/PostActions/NewItemPostActionFactory.cs @@ -21,7 +21,7 @@ namespace Microsoft.Templates.Core.PostActions AddFileNameSearchActions(genInfo, postActions); AddGetMergeFilesFromProjectPostAction(postActions); AddGenerateMergeInfoPostAction(postActions); - AddMergeActions(postActions, $"*{MergePostAction.Extension}*", false); + AddMergeActions(postActions, $"*{MergeConfiguration.Extension}*", false); return postActions; } @@ -30,7 +30,7 @@ namespace Microsoft.Templates.Core.PostActions { var postActions = new List(); - AddGlobalMergeActions(postActions, $"*{MergePostAction.GlobalExtension}*", false); + AddGlobalMergeActions(postActions, $"*{MergeConfiguration.GlobalExtension}*", false); postActions.Add(new SortUsingsPostAction()); postActions.Add(new SortImportsPostAction()); diff --git a/code/src/Core/PostActions/NewProjectPostActionFactory.cs b/code/src/Core/PostActions/NewProjectPostActionFactory.cs index f9f3a52b3..cee5fb5f4 100644 --- a/code/src/Core/PostActions/NewProjectPostActionFactory.cs +++ b/code/src/Core/PostActions/NewProjectPostActionFactory.cs @@ -20,7 +20,7 @@ namespace Microsoft.Templates.Core.PostActions AddFileNameSearchActions(genInfo, postActions); AddPredefinedActions(genInfo, genResult, postActions); - AddMergeActions(postActions, $"*{MergePostAction.Extension}*", true); + AddMergeActions(postActions, $"*{MergeConfiguration.Extension}*", true); AddSearchAndReplaceActions(postActions, $"*{SearchAndReplacePostAction.Extension}*"); return postActions; @@ -30,7 +30,7 @@ namespace Microsoft.Templates.Core.PostActions { var postActions = new List(); - AddGlobalMergeActions(postActions, $"*{MergePostAction.GlobalExtension}*", true); + AddGlobalMergeActions(postActions, $"*{MergeConfiguration.GlobalExtension}*", true); postActions.Add(new SortUsingsPostAction()); postActions.Add(new SortImportsPostAction()); postActions.Add(new AddContextItemsToProjectPostAction()); diff --git a/code/src/Core/PostActions/PostActionFactory.cs b/code/src/Core/PostActions/PostActionFactory.cs index b803f627b..7d1fa9349 100644 --- a/code/src/Core/PostActions/PostActionFactory.cs +++ b/code/src/Core/PostActions/PostActionFactory.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -34,7 +35,7 @@ namespace Microsoft.Templates.Core.PostActions { Directory .EnumerateFiles(GenContext.Current.OutputPath, "*.*", SearchOption.AllDirectories) - .Where(f => Regex.IsMatch(f, MergePostAction.PostactionRegex)) + .Where(f => Regex.IsMatch(f, MergeConfiguration.PostactionRegex)) .ToList() .ForEach(f => postActions.Add(new GetMergeFilesFromProjectPostAction(f))); } @@ -43,7 +44,7 @@ namespace Microsoft.Templates.Core.PostActions { Directory .EnumerateFiles(GenContext.Current.OutputPath, "*.*", SearchOption.AllDirectories) - .Where(f => Regex.IsMatch(f, MergePostAction.PostactionRegex)) + .Where(f => Regex.IsMatch(f, MergeConfiguration.PostactionRegex)) .ToList() .ForEach(f => postActions.Add(new GenerateMergeInfoPostAction(f))); } @@ -71,7 +72,7 @@ namespace Microsoft.Templates.Core.PostActions Directory .EnumerateFiles(GenContext.Current.OutputPath, searchPattern, SearchOption.AllDirectories) .ToList() - .ForEach(f => postActions.Add(new MergePostAction(new MergeConfiguration(f, failOnError)))); + .ForEach(f => AddMergePostAction(postActions, failOnError, f)); } internal void AddFileNameSearchActions(GenInfo genInfo, List postActions) @@ -100,5 +101,22 @@ namespace Microsoft.Templates.Core.PostActions .ToList() .ForEach(f => postActions.Add(new SearchAndReplacePostAction(f))); } + + private static void AddMergePostAction(List postActions, bool failOnError, string f) + { + if (IsResourceDictionaryPostaction(f)) + { + postActions.Add(new MergeResourceDictionaryPostAction(new MergeConfiguration(f, failOnError))); + } + else + { + postActions.Add(new MergePostAction(new MergeConfiguration(f, failOnError))); + } + } + + private static bool IsResourceDictionaryPostaction(string f) + { + return Path.GetExtension(f).ToLower() == ".xaml" & File.ReadAllText(f).StartsWith(MergeConfiguration.ResourceDictionaryMatch); + } } } diff --git a/code/src/Core/Resources/StringRes.Designer.cs b/code/src/Core/Resources/StringRes.Designer.cs index 6bf0e6a0b..0da2de926 100644 --- a/code/src/Core/Resources/StringRes.Designer.cs +++ b/code/src/Core/Resources/StringRes.Designer.cs @@ -150,6 +150,15 @@ namespace Microsoft.Templates.Core.Resources { } } + /// + /// Looks up a localized string similar to Key {0} already defined with different value or elements in file '{1}'. + /// + internal static string FailedMergePostActionKeyAlreadyDefined { + get { + return ResourceManager.GetString("FailedMergePostActionKeyAlreadyDefined", resourceCulture); + } + } + /// /// Looks up a localized string similar to Could not find the expected line `{0}` in file '{1}'. Please merge the content from the postaction file manually.. /// diff --git a/code/src/Core/Resources/StringRes.resx b/code/src/Core/Resources/StringRes.resx index 76bb60018..65e9c1943 100644 --- a/code/src/Core/Resources/StringRes.resx +++ b/code/src/Core/Resources/StringRes.resx @@ -384,4 +384,7 @@ The following changes could not be integrated: {2} Error reading the instance locking file. + + Key {0} already defined with different value or elements in file '{1}' + \ No newline at end of file diff --git a/code/src/UI/Generation/NewItemGenController.cs b/code/src/UI/Generation/NewItemGenController.cs index 5e4efcaae..704d8cc28 100644 --- a/code/src/UI/Generation/NewItemGenController.cs +++ b/code/src/UI/Generation/NewItemGenController.cs @@ -132,7 +132,7 @@ namespace Microsoft.Templates.UI var result = new TempGenerationResult(); var files = Directory .EnumerateFiles(GenContext.Current.OutputPath, "*", SearchOption.AllDirectories) - .Where(f => !Regex.IsMatch(f, MergePostAction.PostactionRegex) && !Regex.IsMatch(f, MergePostAction.FailedPostactionRegex)) + .Where(f => !Regex.IsMatch(f, MergeConfiguration.PostactionRegex) && !Regex.IsMatch(f, MergeConfiguration.FailedPostactionRegex)) .ToList(); foreach (var file in files) diff --git a/code/test/Core.Test/Core.Test.csproj b/code/test/Core.Test/Core.Test.csproj index 4e5c79f95..5629bd49b 100644 --- a/code/test/Core.Test/Core.Test.csproj +++ b/code/test/Core.Test/Core.Test.csproj @@ -146,6 +146,7 @@ + @@ -272,6 +273,40 @@ + + + MSBuild:UpdateDesignTimeXaml + Always + + + + + MSBuild:UpdateDesignTimeXaml + Designer + PreserveNewest + + + + + MSBuild:UpdateDesignTimeXaml + Designer + PreserveNewest + + + + + MSBuild:UpdateDesignTimeXaml + Designer + Always + + + + + MSBuild:UpdateDesignTimeXaml + Always + Designer + + IF EXIST "$(ProjectDir)test.config.json.with.secrets" COPY /Y "$(ProjectDir)test.config.json.with.secrets" "$(TargetDir)test.config.json" diff --git a/code/test/Core.Test/PostActions/Catalog/MergeResourceDictionaryPostactionTest.cs b/code/test/Core.Test/PostActions/Catalog/MergeResourceDictionaryPostactionTest.cs new file mode 100644 index 000000000..9420cc09c --- /dev/null +++ b/code/test/Core.Test/PostActions/Catalog/MergeResourceDictionaryPostactionTest.cs @@ -0,0 +1,71 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Templates.Core.Diagnostics; +using Microsoft.Templates.Core.Gen; +using Microsoft.Templates.Core.PostActions.Catalog.Merge; +using Xunit; + +namespace Microsoft.Templates.Core.Test.PostActions.Catalog +{ + [Trait("ExecutionSet", "Minimum")] + public class MergeResourceDictionaryPostactionTest : IContextProvider + { + public string ProjectName => throw new NotImplementedException(); + + public string OutputPath => Directory.GetCurrentDirectory(); + + public string ProjectPath => throw new NotImplementedException(); + + public List ProjectItems => throw new NotImplementedException(); + + public List FilesToOpen => throw new NotImplementedException(); + + public List FailedMergePostActions => throw new NotImplementedException(); + + public Dictionary> MergeFilesFromProject => throw new NotImplementedException(); + + public Dictionary ProjectMetrics => throw new NotImplementedException(); + + [Fact] + public void MergeResourceDictionaryPostaction() + { + var source = Path.GetFullPath(@".\TestData\Merge\Style.xaml"); + var postaction = Path.GetFullPath(@".\TestData\Merge\Style_postaction.xaml"); + var expected = File.ReadAllText(@".\TestData\Merge\Style_expected.xaml").Replace("\r\n", ""); + + var config = new MergeConfiguration(postaction, true); + + var mergeResourceDictionaryPostAction = new MergeResourceDictionaryPostAction(config); + mergeResourceDictionaryPostAction.Execute(); + + var result = File.ReadAllText(source).Replace("\r\n", ""); + + Assert.Equal(result, expected); + } + + [Fact] + public void MergeResourceDictionaryPostaction_Failing() + { + var source = Path.GetFullPath(@".\TestData\Merge\Style_fail.xaml"); + var postaction = Path.GetFullPath(@".\TestData\Merge\Style_fail_postaction.xaml"); + var expected = File.ReadAllText(@".\TestData\Merge\Style_expected.xaml"); + + GenContext.Current = this; + var config = new MergeConfiguration(postaction, true); + + var mergeResourceDictionaryPostAction = new MergeResourceDictionaryPostAction(config); + + Exception ex = Assert.Throws(() => mergeResourceDictionaryPostAction.Execute()); + + Assert.Equal($"Key PageTitleStyle already defined with different value or elements in file '{source.Replace(GenContext.Current.OutputPath + Path.DirectorySeparatorChar, "")}'", ex.Message); + } + } +} diff --git a/code/test/Core.Test/TestData/Merge/Style.xaml b/code/test/Core.Test/TestData/Merge/Style.xaml new file mode 100644 index 000000000..3e11bd8ac --- /dev/null +++ b/code/test/Core.Test/TestData/Merge/Style.xaml @@ -0,0 +1,7 @@ + + + 28 + 16 + diff --git a/code/test/Core.Test/TestData/Merge/Style_expected.xaml b/code/test/Core.Test/TestData/Merge/Style_expected.xaml new file mode 100644 index 000000000..dbbe6dbde --- /dev/null +++ b/code/test/Core.Test/TestData/Merge/Style_expected.xaml @@ -0,0 +1,19 @@ + + + 28 + 16 + 5 + + + + + diff --git a/code/test/Core.Test/TestData/Merge/Style_fail.xaml b/code/test/Core.Test/TestData/Merge/Style_fail.xaml new file mode 100644 index 000000000..70b04b283 --- /dev/null +++ b/code/test/Core.Test/TestData/Merge/Style_fail.xaml @@ -0,0 +1,15 @@ + + + 28 + 16 + + diff --git a/code/test/Core.Test/TestData/Merge/Style_fail_postaction.xaml b/code/test/Core.Test/TestData/Merge/Style_fail_postaction.xaml new file mode 100644 index 000000000..6274549d2 --- /dev/null +++ b/code/test/Core.Test/TestData/Merge/Style_fail_postaction.xaml @@ -0,0 +1,16 @@ + + + 16 + + 5 + + diff --git a/code/test/Core.Test/TestData/Merge/Style_postaction.xaml b/code/test/Core.Test/TestData/Merge/Style_postaction.xaml new file mode 100644 index 000000000..3004bdf5c --- /dev/null +++ b/code/test/Core.Test/TestData/Merge/Style_postaction.xaml @@ -0,0 +1,16 @@ + + + 16 + 5 + + + diff --git a/docs/getting-started-developers.md b/docs/getting-started-developers.md index 835dcb422..b0effe0ec 100644 --- a/docs/getting-started-developers.md +++ b/docs/getting-started-developers.md @@ -99,6 +99,10 @@ The following list shows which tests are executed in which build. Within the Tem * ExecutionSet=BuildStyleCop * ExecutionSet=TemplateValidation +* VSO 'Templates.Test.Wack' Build (Wack Tests): + * Templates.Test + * ExecutionSet=ManualOnly + To shorten test execution time traits in Templates.Test are run parallel using this [script](../_build/ParallelTestExecution.ps1). To execute this script locally use the following powershell command: