diff --git a/Directory.Build.props b/Directory.Build.props index d3ca50c..7a32adb 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -10,7 +10,7 @@ Copyright Hans van Bakel Tool for converting a MSBuild project file to VS2017 format and beyond. dotnet csproj fsproj vbproj msbuild conversion vs2015 vs14 vs15 vs2017 - 4.1.0-beta.4 + 4.1.0-beta.5 4.1.0.0 diff --git a/Project2015To2017.Core/Definition/Project.cs b/Project2015To2017.Core/Definition/Project.cs index fbd54c4..ec130d7 100644 --- a/Project2015To2017.Core/Definition/Project.cs +++ b/Project2015To2017.Core/Definition/Project.cs @@ -88,5 +88,35 @@ public DirectoryInfo NuGetPackagesPath public IReadOnlyList AssemblyAttributeProperties { get; set; } = Array.Empty(); public IReadOnlyList IntermediateOutputPaths { get; set; } + + private sealed class ProjectNameFilePathEqualityComparer : IEqualityComparer + { + public bool Equals(Project x, Project y) + { + if (ReferenceEquals(x, y)) return true; + if (x is null) return false; + if (y is null) return false; + if (x.GetType() != y.GetType()) return false; + return string.Equals(x.ProjectName, y.ProjectName, StringComparison.InvariantCultureIgnoreCase) && + string.Equals(x.FilePath.FullName, y.FilePath.FullName, + StringComparison.InvariantCultureIgnoreCase); + } + + public int GetHashCode(Project obj) + { + var a = obj.ProjectName != null + ? StringComparer.InvariantCultureIgnoreCase.GetHashCode(obj.ProjectName) + : 0; + var b = obj.FilePath != null + ? StringComparer.InvariantCultureIgnoreCase.GetHashCode(obj.FilePath.FullName) + : 0; + unchecked + { + return (a * 397) ^ b; + } + } + } + + public static IEqualityComparer ProjectNameFilePathComparer { get; } = new ProjectNameFilePathEqualityComparer(); } } diff --git a/Project2015To2017.Core/Definition/Solution.cs b/Project2015To2017.Core/Definition/Solution.cs index 87ab9d5..a603689 100644 --- a/Project2015To2017.Core/Definition/Solution.cs +++ b/Project2015To2017.Core/Definition/Solution.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.IO; using NuGet.Configuration; @@ -31,5 +32,24 @@ public DirectoryInfo NuGetPackagesPath return new DirectoryInfo(Extensions.MaybeAdjustFilePath(path, solutionFolder)); } } + + private sealed class FilePathEqualityComparer : IEqualityComparer + { + public bool Equals(Solution x, Solution y) + { + if (ReferenceEquals(x, y)) return true; + if (x is null) return false; + if (y is null) return false; + if (x.GetType() != y.GetType()) return false; + return string.Equals(x.FilePath.FullName, y.FilePath.FullName, StringComparison.InvariantCultureIgnoreCase); + } + + public int GetHashCode(Solution obj) + { + return (obj.FilePath != null ? StringComparer.InvariantCultureIgnoreCase.GetHashCode(obj.FilePath.FullName) : 0); + } + } + + public static IEqualityComparer FilePathComparer { get; } = new FilePathEqualityComparer(); } } diff --git a/Project2015To2017.Core/Extensions.cs b/Project2015To2017.Core/Extensions.cs index aba6392..d4a1eef 100644 --- a/Project2015To2017.Core/Extensions.cs +++ b/Project2015To2017.Core/Extensions.cs @@ -1,12 +1,7 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Runtime.InteropServices; -using System.Text; using System.Xml.Linq; -using Microsoft.Extensions.Logging; -using Project2015To2017.Transforms; namespace Project2015To2017 { diff --git a/Project2015To2017.Core/Transforms/BasicSimplifyTransformationSet.cs b/Project2015To2017.Core/Transforms/BasicSimplifyTransformationSet.cs index 4e876b8..4cc229b 100644 --- a/Project2015To2017.Core/Transforms/BasicSimplifyTransformationSet.cs +++ b/Project2015To2017.Core/Transforms/BasicSimplifyTransformationSet.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Text; using Microsoft.Extensions.Logging; namespace Project2015To2017.Transforms @@ -22,6 +21,7 @@ public IReadOnlyCollection Transformations( { new PropertyDeduplicationTransformation(), new PropertySimplificationTransformation(targetVisualStudioVersion), + new ServiceFilterTransformation(targetVisualStudioVersion), new PrimaryProjectPropertiesUpdateTransformation(), new EmptyGroupRemoveTransformation(), }; diff --git a/Project2015To2017.Core/Transforms/PropertySimplificationTransformation.cs b/Project2015To2017.Core/Transforms/PropertySimplificationTransformation.cs index 494c454..6b33557 100644 --- a/Project2015To2017.Core/Transforms/PropertySimplificationTransformation.cs +++ b/Project2015To2017.Core/Transforms/PropertySimplificationTransformation.cs @@ -9,7 +9,7 @@ namespace Project2015To2017.Transforms { - public sealed class PropertySimplificationTransformation : ILegacyOnlyProjectTransformation + public sealed class PropertySimplificationTransformation : ITransformation { private static readonly string[] IgnoreProjectNameValues = { @@ -17,16 +17,7 @@ public sealed class PropertySimplificationTransformation : ILegacyOnlyProjectTra "$(ProjectName)" }; - private static readonly string[] KnownTestFrameworkIds = - { - "Microsoft.NET.Test.Sdk", - "xUnit.Core", - "xUnit", - "NUnit", - }; - private readonly Version targetVisualStudioVersion; - private static readonly Version Vs15TestServiceFixVersion = new Version(15, 7); public PropertySimplificationTransformation(Version targetVisualStudioVersion = null) { @@ -196,7 +187,6 @@ when ValidateDefaultValue("{fae04ec0-301f-11d3-bf4b-00c04f79efbc}"): case "AssemblyName" when IsDefaultProjectNameValued(): case "TargetName" when IsDefaultProjectNameValued(): case "ProjectGuid" when ProjectGuidMatchesSolutionProjectGuid(): - case "Service" when IncludeMatchesSpecificGuid(): { removeQueue.Add(child); break; @@ -273,24 +263,6 @@ bool IsDefaultProjectNameValued() IgnoreProjectNameValues.Contains(child.Value) ); } - - bool IncludeMatchesSpecificGuid() - { - // This is not required as of VS15.7 and above, but we might target VS15.0 - if (targetVisualStudioVersion < Vs15TestServiceFixVersion) - if (!project.PackageReferences.Any(IsKnownTestProvider)) - return false; - - return child.Attribute("Include")?.Value == "{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}"; - - bool IsKnownTestProvider(PackageReference x) - { - // In theory, we should be checking versions of these test frameworks - // to see, if they have the fix included. - - return KnownTestFrameworkIds.Contains(x.Id); - } - } } // we cannot remove elements correctly while iterating through elements, 2nd pass is needed diff --git a/Project2015To2017.Core/Transforms/ServiceFilterTransformation.cs b/Project2015To2017.Core/Transforms/ServiceFilterTransformation.cs new file mode 100644 index 0000000..eebde78 --- /dev/null +++ b/Project2015To2017.Core/Transforms/ServiceFilterTransformation.cs @@ -0,0 +1,58 @@ +using System; +using System.Linq; +using System.Xml.Linq; +using Project2015To2017.Definition; + +namespace Project2015To2017.Transforms +{ + public sealed class ServiceFilterTransformation : ITransformation + { + private static readonly string[] KnownTestFrameworkIds = + { + "Microsoft.NET.Test.Sdk", + "xUnit.Core", + "xUnit", + "NUnit", + }; + + private static readonly Version Vs15TestServiceFixVersion = new Version(15, 7); + + private readonly Version targetVisualStudioVersion; + + public ServiceFilterTransformation(Version targetVisualStudioVersion) + { + this.targetVisualStudioVersion = targetVisualStudioVersion ?? throw new ArgumentNullException(nameof(targetVisualStudioVersion)); + } + + public void Transform(Project definition) + { + var removeQueue = definition.ItemGroups.ElementsAnyNamespace("Service").Where(IncludeMatchesSpecificGuid).ToArray(); + + foreach (var element in removeQueue) + { + element.Remove(); + } + + bool IncludeMatchesSpecificGuid(XElement child) + { + if (string.Equals(child.Attribute("Include")?.Value, "{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}", + StringComparison.InvariantCultureIgnoreCase)) + { + // Fix is included with VS15.7 and above, but we might target VS15.0 + // All known test providers also have a fix included + return targetVisualStudioVersion >= Vs15TestServiceFixVersion || definition.PackageReferences.Any(IsKnownTestProvider); + } + + return false; + } + } + + private static bool IsKnownTestProvider(PackageReference x) + { + // In theory, we should be checking versions of these test frameworks + // to see, if they have the fix included. + + return KnownTestFrameworkIds.Contains(x.Id); + } + } +} diff --git a/Project2015To2017.Migrate2017.Library/Transforms/FrameworkReferencesTransformation.cs b/Project2015To2017.Migrate2017.Library/Transforms/FrameworkReferencesTransformation.cs index 39f0efa..44f87c1 100644 --- a/Project2015To2017.Migrate2017.Library/Transforms/FrameworkReferencesTransformation.cs +++ b/Project2015To2017.Migrate2017.Library/Transforms/FrameworkReferencesTransformation.cs @@ -8,7 +8,7 @@ namespace Project2015To2017.Migrate2017.Transforms { public sealed class FrameworkReferencesTransformation : ILegacyOnlyProjectTransformation { - private const string SdkExtrasVersion = "MSBuild.Sdk.Extras/1.6.65"; + private const string SdkExtrasVersion = "MSBuild.Sdk.Extras/1.6.68"; private static readonly Guid XamarinAndroid = Guid.ParseExact("EFBA0AD7-5A72-4C68-AF49-83D382785DCF", "D"); private static readonly Guid XamarinIos = Guid.ParseExact("6BC8ED88-2882-458C-8E55-DFD12B67127B", "D"); private static readonly Guid Uap = Guid.ParseExact("A5A43C5B-DE2A-4C0C-9213-0A381AF9435A", "D"); diff --git a/Project2015To2017.Migrate2017.Library/Transforms/UpgradeTestServiceTransformation.cs b/Project2015To2017.Migrate2017.Library/Transforms/UpgradeTestServiceTransformation.cs index 32198a9..bf4d2aa 100644 --- a/Project2015To2017.Migrate2017.Library/Transforms/UpgradeTestServiceTransformation.cs +++ b/Project2015To2017.Migrate2017.Library/Transforms/UpgradeTestServiceTransformation.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Immutable; using System.Linq; using Project2015To2017.Definition; using Project2015To2017.Transforms; @@ -10,10 +9,10 @@ public sealed class UpgradeTestServiceTransformation : ITransformation { public void Transform(Project definition) { - var removeQueue = definition.PropertyGroups + var removeQueue = definition.ItemGroups .ElementsAnyNamespace("Service") .Where(x => !string.IsNullOrEmpty(x.Value) && string.Equals(x.Value, "{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}", StringComparison.OrdinalIgnoreCase)) - .ToImmutableArray(); + .ToArray(); foreach (var element in removeQueue) { diff --git a/Project2015To2017.Migrate2017.Tool/CommandLogic.cs b/Project2015To2017.Migrate2017.Tool/CommandLogic.cs deleted file mode 100644 index 6d3443f..0000000 --- a/Project2015To2017.Migrate2017.Tool/CommandLogic.cs +++ /dev/null @@ -1,370 +0,0 @@ -using System; -using DotNet.Globbing; -using Serilog; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.IO; -using System.Linq; -using System.Xml; -using System.Xml.Linq; -using Microsoft.Extensions.Logging; -using Project2015To2017.Analysis; -using Project2015To2017.Definition; -using Project2015To2017.Transforms; -using Project2015To2017.Writing; -using Project2015To2017.Migrate2017; - -namespace Project2015To2017.Migrate2017.Tool -{ - public class CommandLogic - { - private readonly PatternProcessor globProcessor = (converter, pattern, callback, self) => - { - Log.Verbose("Falling back to globbing"); - self.DoProcessableFileSearch(); - var glob = Glob.Parse(pattern); - Log.Verbose("Parsed glob {Glob}", glob); - foreach (var (path, extension) in self.Files) - { - if (!glob.IsMatch(path)) continue; - var file = new FileInfo(path); - callback(file, extension); - } - - return true; - }; - - private readonly MigrationFacility facility; - - public CommandLogic() - { - var genericLogger = new Serilog.Extensions.Logging.SerilogLoggerProvider().CreateLogger(nameof(Serilog)); - facility = new MigrationFacility(genericLogger, globProcessor); - } - - public void ExecuteEvaluate( - IReadOnlyCollection items, - ConversionOptions conversionOptions) - { - facility.ExecuteEvaluate(items, conversionOptions); - } - - public void ExecuteMigrate( - IReadOnlyCollection items, - bool noBackup, - ConversionOptions conversionOptions) - { - var writeOptions = new ProjectWriteOptions { MakeBackups = !noBackup }; - facility.ExecuteMigrate(items, conversionOptions, writeOptions); - } - - public void ExecuteAnalyze( - IReadOnlyCollection items, - ConversionOptions conversionOptions) - { - facility.ExecuteAnalyze(items, conversionOptions); - } - - public void ExecuteWizard( - IReadOnlyCollection items, - ConversionOptions conversionOptions) - { - conversionOptions.UnknownTargetFrameworkCallback = WizardUnknownTargetFrameworkCallback; - - var (projects, solutions) = - facility.ParseProjects(items, BasicReadTransformationSet.Instance, conversionOptions); - - if (projects.Count == 0) - { - Log.Information("No projects have been found to match your criteria."); - return; - } - - var (modern, legacy) = projects.Split(x => x.IsModernProject); - foreach (var projectPath in modern.Select(x => x.FilePath)) - { - Log.Information("Project {ProjectPath} is already CPS-based", projectPath); - } - - foreach (var projectPath in solutions.SelectMany(x => x.UnsupportedProjectPaths)) - { - Log.Warning("Project {ProjectPath} migration is not supported at the moment", - projectPath); - } - - facility.DoAnalysis(projects, new AnalysisOptions(DiagnosticSet.All)); - - if (legacy.Count > 0) - { - var doBackups = AskBinaryChoice("Would you like to create backups?"); - - var writer = new ProjectWriter(facility.Logger, new ProjectWriteOptions { MakeBackups = doBackups }); - - var transformations = new ChainTransformationSet( - new BasicSimplifyTransformationSet(Vs15TransformationSet.TargetVisualStudioVersion), - Vs15TransformationSet.TrueInstance) - .CollectAndOrderTransformations(facility.Logger, conversionOptions); - - foreach (var project in legacy) - { - using (facility.Logger.BeginScope(project.FilePath)) - { - var projectName = Path.GetFileNameWithoutExtension(project.FilePath.Name); - Log.Information("Converting {ProjectName}...", projectName); - - if (!project.Valid) - { - Log.Error("Project {ProjectName} is marked as invalid, skipping...", projectName); - continue; - } - - foreach (var transformation in transformations.WhereSuitable(project, conversionOptions)) - { - try - { - transformation.Transform(project); - } - catch (Exception e) - { - Log.Error(e, "Transformation {Item} has thrown an exception, skipping...", - transformation.GetType().Name); - } - } - - if (!writer.TryWrite(project)) - continue; - Log.Information("Project {ProjectName} has been converted", projectName); - } - } - } - else - { - var writer = new ProjectWriter(facility.Logger, new ProjectWriteOptions()); - - Log.Information("It appears you already have everything converted to CPS."); - if (AskBinaryChoice("Would you like to process CPS projects to clean up and reformat them?")) - { - var transformations = - new BasicSimplifyTransformationSet(Vs15TransformationSet.TargetVisualStudioVersion) - .CollectAndOrderTransformations(facility.Logger, conversionOptions); - - foreach (var project in modern) - { - using (facility.Logger.BeginScope(project.FilePath)) - { - var projectName = Path.GetFileNameWithoutExtension(project.FilePath.Name); - Log.Information("Processing {ProjectName}...", projectName); - - if (!project.Valid) - { - Log.Error("Project {ProjectName} is marked as invalid, skipping...", projectName); - continue; - } - - foreach (var transformation in transformations.WhereSuitable(project, conversionOptions)) - { - try - { - transformation.Transform(project); - } - catch (Exception e) - { - Log.Error(e, "Transformation {Item} has thrown an exception, skipping...", - transformation.GetType().Name); - } - } - - if (!writer.TryWrite(project)) - continue; - Log.Information("Project {ProjectName} has been processed", projectName); - } - } - } - } - - conversionOptions.ProjectCache?.Purge(); - - (projects, _) = facility.ParseProjects(items, BasicReadTransformationSet.Instance, conversionOptions); - - Log.Information("Modernization can be progressed a little further, but it might lead to unexpected behavioral changes."); - if (AskBinaryChoice("Would you like to modernize projects?")) - { - var doBackups = AskBinaryChoice("Would you like to create backups?"); - - var writer = new ProjectWriter(facility.Logger, new ProjectWriteOptions { MakeBackups = doBackups }); - - var transformations = new ChainTransformationSet( - new BasicSimplifyTransformationSet(Vs15TransformationSet.TargetVisualStudioVersion), - Vs15ModernizationTransformationSet.TrueInstance) - .CollectAndOrderTransformations(facility.Logger, conversionOptions); - - foreach (var project in projects) - { - using (facility.Logger.BeginScope(project.FilePath)) - { - var projectName = Path.GetFileNameWithoutExtension(project.FilePath.Name); - Log.Information("Modernizing {ProjectName}...", projectName); - - if (!project.Valid) - { - Log.Error("Project {ProjectName} is marked as invalid, skipping...", projectName); - continue; - } - - foreach (var transformation in transformations.WhereSuitable(project, conversionOptions)) - { - try - { - transformation.Transform(project); - } - catch (Exception e) - { - Log.Error(e, "Transformation {Item} has thrown an exception, skipping...", - transformation.GetType().Name); - } - } - - if (!writer.TryWrite(project)) - continue; - Log.Information("Project {ProjectName} has been modernized", projectName); - } - } - - conversionOptions.ProjectCache?.Purge(); - - (projects, _) = facility.ParseProjects(items, BasicReadTransformationSet.Instance, conversionOptions); - } - - var diagnostics = new DiagnosticSet(Vs15DiagnosticSet.All); - diagnostics.ExceptWith(DiagnosticSet.All); - facility.DoAnalysis(projects, new AnalysisOptions(diagnostics)); - } - - private readonly List<(ImmutableHashSet<(string when, string tfms)> variant, ImmutableArray answer)> - previousFrameworkResolutionAnswers = - new List<(ImmutableHashSet<(string, string)>, ImmutableArray)>(); - - private bool WizardUnknownTargetFrameworkCallback(Project project, - IReadOnlyList<(IReadOnlyList frameworks, XElement source, string condition)> foundTargetFrameworks) - { - Log.Warning("Cannot unambiguously determine target frameworks for {ProjectName}.", project.FilePath); - - var currentVariant = new HashSet<(string when, string tfms)>(); - - if (foundTargetFrameworks.Count > 0) - { - Log.Information("Found {Count} possible variants:", foundTargetFrameworks.Count); - foreach (var (frameworks, source, condition) in foundTargetFrameworks) - { - if (source is IXmlLineInfo elementOnLine && elementOnLine.HasLineInfo()) - { - Log.Information("{Line}: {TFMs} ({When})", elementOnLine.LineNumber, frameworks, condition); - } - else - { - Log.Information("{TFMs} ({When})", frameworks, condition); - } - - currentVariant.Add((condition, - string.Join(";", frameworks.ToImmutableSortedSet()).ToLowerInvariant())); - } - - foreach (var (variant, answer) in previousFrameworkResolutionAnswers) - { - if (!currentVariant.SetEquals(variant)) - continue; - - Log.Information("You have previously selected {FormerChoice} for that combination.", answer); - if (AskBinaryChoice("Would you like to use this framework set?")) - { - foreach (var tfm in answer) - { - project.TargetFrameworks.Add(tfm); - } - - return true; - } - - break; - } - } - - Log.Information("Please, enter target frameworks to use (comma or space separated):"); - Console.Out.Flush(); - var tfms = Console.ReadLine() - ?.Trim() - .Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) - .Where(s => !string.IsNullOrWhiteSpace(s)) - .ToImmutableArray() ?? ImmutableArray.Empty; - - if (tfms.IsDefaultOrEmpty) - { - Log.Warning("You didn't specify any TFMs to use."); - return AskBinaryChoice("Attempt to continue without specifying them?", defaultChoiceIsYes: false); - } - - if (foundTargetFrameworks.Count > 0) - { - previousFrameworkResolutionAnswers.Add((currentVariant.ToImmutableHashSet(), tfms)); - } - - foreach (var tfm in tfms) - { - project.TargetFrameworks.Add(tfm); - } - - return true; - } - - private static bool AskBinaryChoice(string question, string yes = "Yes", string no = "No", - bool defaultChoiceIsYes = true) - { - Console.Out.Flush(); - var yesCharLower = char.ToLowerInvariant(yes[0]); - var noCharLower = char.ToLowerInvariant(no[0]); - var yesChar = defaultChoiceIsYes ? char.ToUpperInvariant(yes[0]) : yesCharLower; - var noChar = defaultChoiceIsYes ? noCharLower : char.ToUpperInvariant(no[0]); - Console.Write($"{question} ({yesChar}/{noChar}) "); - Console.Out.Flush(); - bool? result = null; - while (!result.HasValue) - { - result = DetermineKeyChoice(Console.ReadKey(true), yesCharLower, noCharLower, defaultChoiceIsYes); - } - - var realResult = result.Value; - Console.WriteLine(realResult ? yes : no); - Console.Out.Flush(); - return realResult; - } - - private static bool? DetermineKeyChoice(ConsoleKeyInfo info, char yesChar, char noChar, bool defaultChoice) - { - switch (char.ToLowerInvariant(info.KeyChar)) - { - case 'y': - case 't': - case '1': - case char c when c == yesChar: - return true; - case 'n': - case 'f': - case '0': - case char c when c == noChar: - return false; - } - - switch (info.Key) - { - case ConsoleKey.LeftArrow: - return true; - case ConsoleKey.RightArrow: - return false; - case ConsoleKey.Enter: - return defaultChoice; - } - - return null; - } - } -} \ No newline at end of file diff --git a/Project2015To2017.Migrate2017.Tool/Program.cs b/Project2015To2017.Migrate2017.Tool/Program.cs index 66b0977..a801256 100644 --- a/Project2015To2017.Migrate2017.Tool/Program.cs +++ b/Project2015To2017.Migrate2017.Tool/Program.cs @@ -1,11 +1,10 @@ +using System; using Microsoft.DotNet.Cli.CommandLine; +using Project2015To2017.Analysis; using Project2015To2017.Caching; +using Project2015To2017.Transforms; using Serilog; -using Serilog.Core; using Serilog.Events; -using System; -using System.IO; -using System.Linq; using static Microsoft.DotNet.Cli.CommandLine.Accept; using static Microsoft.DotNet.Cli.CommandLine.Create; @@ -13,14 +12,9 @@ namespace Project2015To2017.Migrate2017.Tool { internal static class Program { - static int Main(string[] args) + private static int Main(string[] args) { - Log.Logger = new LoggerConfiguration() - .Enrich.FromLogContext() - .Enrich.WithDemystifiedStackTraces() - .MinimumLevel.ControlledBy(verbosity) - .WriteTo.Console() - .CreateLogger(); + ProgramBase.CreateLogger(); try { @@ -51,40 +45,13 @@ static int Main(string[] args) } } - private static readonly LoggingLevelSwitch verbosity = new LoggingLevelSwitch(); - private static int ProcessArgs(ParseResult result) { result.ShowHelpOrErrorIfAppropriate(); var command = result.AppliedCommand(); - var verbosityValue = result["dotnet-migrate-2017"].ValueOrDefault("verbosity")?.Trim().ToLowerInvariant() ?? "normal"; - switch (verbosityValue) - { - case "q": - case "quiet": - verbosity.MinimumLevel = LogEventLevel.Fatal + 1; - break; - case "m": - case "minimal": - verbosity.MinimumLevel = LogEventLevel.Warning; - break; - case "n": - case "normal": - verbosity.MinimumLevel = LogEventLevel.Information; - break; - case "d": - case "detailed": - verbosity.MinimumLevel = LogEventLevel.Debug; - break; - // ReSharper disable once StringLiteralTypo - case "diag": - case "diagnostic": - verbosity.MinimumLevel = LogEventLevel.Verbose; - break; - default: - throw new CommandParsingException($"Unknown verbosity level '{verbosityValue}'.", result.Command().HelpView().TrimEnd()); - } + var globalOptions = result["dotnet-migrate-2017"]; + ProgramBase.ApplyVerbosity(result, globalOptions); Log.Verbose(result.Diagram()); @@ -111,13 +78,28 @@ private static int ProcessArgs(ParseResult result) switch (command.Name) { case "wizard": - logic.ExecuteWizard(items, conversionOptions); + var diagnostics = new DiagnosticSet(Vs15DiagnosticSet.All); + diagnostics.ExceptWith(DiagnosticSet.All); + var sets = new WizardTransformationSets + { + MigrateSet = new ChainTransformationSet( + new BasicSimplifyTransformationSet(Vs15TransformationSet.TargetVisualStudioVersion), + Vs15TransformationSet.TrueInstance), + ModernCleanUpSet = new BasicSimplifyTransformationSet( + Vs15TransformationSet.TargetVisualStudioVersion), + ModernizeSet = new ChainTransformationSet( + new BasicSimplifyTransformationSet(Vs15TransformationSet.TargetVisualStudioVersion), + Vs15ModernizationTransformationSet.TrueInstance), + Diagnostics = diagnostics + }; + + logic.ExecuteWizard(items, conversionOptions, sets); break; case "evaluate": - logic.ExecuteEvaluate(items, conversionOptions); + logic.ExecuteEvaluate(items, conversionOptions, Vs15TransformationSet.Instance, new AnalysisOptions(Vs15DiagnosticSet.All)); break; case "analyze": - logic.ExecuteAnalyze(items, conversionOptions); + logic.ExecuteAnalyze(items, conversionOptions, new AnalysisOptions(Vs15DiagnosticSet.All)); break; case "migrate": conversionOptions.AppendTargetFrameworkToOutputPath = !command.ValueOrDefault("old-output-path"); @@ -126,155 +108,24 @@ private static int ProcessArgs(ParseResult result) if (forceTransformations != null) conversionOptions.ForceDefaultTransforms = forceTransformations; - logic.ExecuteMigrate(items, command.ValueOrDefault("no-backup"), conversionOptions); + logic.ExecuteMigrate(items, command.ValueOrDefault("no-backup"), conversionOptions, Vs15TransformationSet.Instance); break; } return result.Execute().Code; } - private static readonly Parser Instance = new Parser(options: RootCommand()); - - private static ArgumentsRule DefaultToCurrentDirectory(this ArgumentsRule rule) => - rule.With(defaultValue: () => NuGet.Common.PathUtility.EnsureTrailingSlash(Directory.GetCurrentDirectory())); - private static Command RootCommand() => Command("dotnet-migrate-2017", ".NET Project Migration Tool", NoArguments(), - Wizard(), - Evaluate(), - Migrate(), - Analyze(), - HelpOption(), - VerbosityOption()); - - private static ArgumentsRule ItemsArgument => ZeroOrMoreArguments() - .With("Project/solution file paths or glob patterns", "items") - .DefaultToCurrentDirectory(); - - private static Option TargetFrameworksOption => Option( - "-t|--target-frameworks", - "Override project target frameworks with ones specified. Specify multiple times for multiple target frameworks.", - OneOrMoreArguments() - .With("Target frameworks to be used instead of the ones in source projects", "frameworks")); - - private static Option KeepAssemblyInfoOption => Option("-a|--keep-assembly-info", - "Keep assembly attributes in AssemblyInfo file instead of moving them to project file."); - - private static Option ForceOption => Option("-f|--force", - "Force a conversion even if not all preconditions are met."); - - private static Command Evaluate() => - Command("evaluate", - "Examine the projects potential to be converted before actual migration", - ItemsArgument, - TargetFrameworksOption, - HelpOption()); - - private static Command Migrate() => - Command("migrate", - "Migrate projects to VS2017+ CPS format (non-interactive)", - ItemsArgument, - Option("-n|--no-backup", - "Skip moving project.json, global.json, and *.xproj to a `Backup` directory after successful migration."), - ForceOption, - KeepAssemblyInfoOption, - TargetFrameworksOption, - Option("-o|--old-output-path", - "Preserve legacy behavior by not creating a subfolder with the target framework in the output path."), - Option( - "-ft|--force-transformations", - "Force execution of transformations despite project conversion state by their specified names. " + - "Specify multiple times for multiple enforced transformations.", - OneOrMoreArguments() - .With("Transformation names to enforce execution", "names")), - HelpOption()); - - private static Command Analyze() => - Command("analyze", - "Do the analysis run and output diagnostics", - ItemsArgument, - HelpOption()); - - private static Command Wizard() => - Command("wizard", - "Launch interactive migration wizard (recommended)", - ItemsArgument, - ForceOption, - KeepAssemblyInfoOption, - HelpOption()); - - private static Option HelpOption() => - Option("-h|--help", - "Show help information", - NoArguments()); - - private static Option VerbosityOption() => - Option("-v|--verbosity", - // ReSharper disable StringLiteralTypo - "Set the verbosity level of the command. Allowed values are q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic]", - // ReSharper restore StringLiteralTypo - AnyOneOf("q", "quiet", - "m", "minimal", - "n", "normal", - "d", "detailed", - // ReSharper disable once StringLiteralTypo - "diag", "diagnostic") - .With(name: "LEVEL")); - - public static void ShowHelpOrErrorIfAppropriate(this ParseResult parseResult) - { - parseResult.ShowHelpIfRequested(); + ProgramBase.Wizard(), + ProgramBase.Evaluate(), + ProgramBase.Migrate(), + ProgramBase.Analyze(), + ProgramBase.HelpOption(), + ProgramBase.VerbosityOption()); - if (parseResult.Errors.Any()) - { - throw new CommandParsingException( - string.Join(Environment.NewLine, - parseResult.Errors.Select(e => e.Message)), - parseResult.Command()?.HelpView().TrimEnd()); - } - } - - private static void ShowHelpIfRequested(this ParseResult parseResult) - { - var appliedCommand = parseResult.AppliedCommand(); - - if (appliedCommand.HasOption("help") || - appliedCommand.Arguments.Contains("-?") || - appliedCommand.Arguments.Contains("/?")) - { - throw new HelpException(parseResult.Command().HelpView().TrimEnd()); - } - } - - public static T ValueOrDefault(this AppliedOption parseResult, string alias) - { - return parseResult - .AppliedOptions - .Where(o => o.HasAlias(alias)) - .Select(o => o.Value()) - .SingleOrDefault(); - } - - /// Allows control flow to be interrupted in order to display help in the console. - public sealed class HelpException : Exception - { - public HelpException(string message) : base(message) - { - } - } - - private sealed class CommandParsingException : Exception - { - public CommandParsingException( - string message, - string helpText = null) : base(message) - { - HelpText = helpText ?? ""; - } - - public string HelpText { get; } - } + private static readonly Parser Instance = new Parser(options: RootCommand()); } } \ No newline at end of file diff --git a/Project2015To2017.Migrate2017.Tool/Project2015To2017.Migrate2017.Tool.csproj b/Project2015To2017.Migrate2017.Tool/Project2015To2017.Migrate2017.Tool.csproj index 39fc6a2..9c96e94 100644 --- a/Project2015To2017.Migrate2017.Tool/Project2015To2017.Migrate2017.Tool.csproj +++ b/Project2015To2017.Migrate2017.Tool/Project2015To2017.Migrate2017.Tool.csproj @@ -1,4 +1,4 @@ - + net461;netcoreapp2.1 @@ -19,6 +19,8 @@ + + diff --git a/Project2015To2017.Migrate2019.Library/Transforms/Vs16FrameworkReferencesTransformation.cs b/Project2015To2017.Migrate2019.Library/Transforms/Vs16FrameworkReferencesTransformation.cs index e6cedc0..3245bba 100644 --- a/Project2015To2017.Migrate2019.Library/Transforms/Vs16FrameworkReferencesTransformation.cs +++ b/Project2015To2017.Migrate2019.Library/Transforms/Vs16FrameworkReferencesTransformation.cs @@ -8,7 +8,7 @@ namespace Project2015To2017.Migrate2019.Library.Transforms { public sealed class Vs16FrameworkReferencesTransformation : ILegacyOnlyProjectTransformation { - private const string SdkExtrasVersion = "MSBuild.Sdk.Extras/2.0.0-preview.21"; + private const string SdkExtrasVersion = "MSBuild.Sdk.Extras/2.0.31"; private const string WindowsDesktopVersion = "Microsoft.NET.Sdk.WindowsDesktop"; private static readonly Guid XamarinAndroid = Guid.ParseExact("EFBA0AD7-5A72-4C68-AF49-83D382785DCF", "D"); private static readonly Guid XamarinIos = Guid.ParseExact("6BC8ED88-2882-458C-8E55-DFD12B67127B", "D"); diff --git a/Project2015To2017.Migrate2019.Library/Vs16DiagnosticSet.cs b/Project2015To2017.Migrate2019.Library/Vs16DiagnosticSet.cs new file mode 100644 index 0000000..5ef8910 --- /dev/null +++ b/Project2015To2017.Migrate2019.Library/Vs16DiagnosticSet.cs @@ -0,0 +1,15 @@ +using Project2015To2017.Analysis; +using Project2015To2017.Migrate2017; + +namespace Project2015To2017.Migrate2019.Library +{ + public static class Vs16DiagnosticSet + { + public static readonly DiagnosticSet All = new DiagnosticSet(); + + static Vs16DiagnosticSet() + { + All.UnionWith(Vs15DiagnosticSet.All); + } + } +} diff --git a/Project2015To2017.Migrate2019.Library/Vs16TransformationSet.cs b/Project2015To2017.Migrate2019.Library/Vs16TransformationSet.cs index 31adb8f..b5e051b 100644 --- a/Project2015To2017.Migrate2019.Library/Vs16TransformationSet.cs +++ b/Project2015To2017.Migrate2019.Library/Vs16TransformationSet.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using Microsoft.Extensions.Logging; using Project2015To2017.Migrate2017.Transforms; +using Project2015To2017.Migrate2019.Library.Transforms; using Project2015To2017.Transforms; namespace Project2015To2017.Migrate2019.Library @@ -31,8 +32,8 @@ public IReadOnlyCollection Transformations( new TargetFrameworkReplaceTransformation( conversionOptions.TargetFrameworks, conversionOptions.AppendTargetFrameworkToOutputPath), - // VS15 migration - new FrameworkReferencesTransformation(), + // VS16 migration + new Vs16FrameworkReferencesTransformation(), new TestProjectPackageReferenceTransformation(logger), new AssemblyFilterPackageReferencesTransformation(), new AssemblyFilterHintedPackageReferencesTransformation(), diff --git a/Project2015To2017.Migrate2019.Tool/Program.cs b/Project2015To2017.Migrate2019.Tool/Program.cs new file mode 100644 index 0000000..cfdcbf8 --- /dev/null +++ b/Project2015To2017.Migrate2019.Tool/Program.cs @@ -0,0 +1,133 @@ +using System; +using Microsoft.DotNet.Cli.CommandLine; +using Project2015To2017.Analysis; +using Project2015To2017.Caching; +using Project2015To2017.Migrate2017.Tool; +using Project2015To2017.Migrate2019.Library; +using Project2015To2017.Transforms; +using Serilog; +using Serilog.Events; +using static Microsoft.DotNet.Cli.CommandLine.Accept; +using static Microsoft.DotNet.Cli.CommandLine.Create; + +namespace Project2015To2017.Migrate2019.Tool +{ + internal static class Program + { + private static int Main(string[] args) + { + ProgramBase.CreateLogger(); + + try + { + var result = Instance.Parse(args); + return ProcessArgs(result); + } + catch (HelpException e) + { + Log.Information(e.Message); + return 0; + } + catch (Exception e) + { + if (Log.IsEnabled(LogEventLevel.Debug)) + Log.Fatal(e, "Fatal exception occurred"); + else + Log.Fatal(e.Message); + if (e is CommandParsingException commandParsingException) + { + Log.Information(commandParsingException.HelpText); + } + + return 1; + } + finally + { + Log.CloseAndFlush(); + } + } + + private static int ProcessArgs(ParseResult result) + { + result.ShowHelpOrErrorIfAppropriate(); + + var command = result.AppliedCommand(); + var globalOptions = result["dotnet-migrate-2019"]; + ProgramBase.ApplyVerbosity(result, globalOptions); + + Log.Verbose(result.Diagram()); + + var items = command.Value(); + + var conversionOptions = new ConversionOptions + { + ProjectCache = new DefaultProjectCache(), + Force = command.ValueOrDefault("force"), + KeepAssemblyInfo = command.ValueOrDefault("keep-assembly-info") + }; + + switch (command.Name) + { + case "evaluate": + case "migrate": + var frameworks = command.ValueOrDefault("target-frameworks"); + if (frameworks != null) + conversionOptions.TargetFrameworks = frameworks; + break; + } + + var logic = new CommandLogic(); + switch (command.Name) + { + case "wizard": + var diagnostics = new DiagnosticSet(Vs16DiagnosticSet.All); + diagnostics.ExceptWith(DiagnosticSet.All); + var sets = new WizardTransformationSets + { + MigrateSet = new ChainTransformationSet( + new BasicSimplifyTransformationSet(Vs16TransformationSet.TargetVisualStudioVersion), + Vs16TransformationSet.TrueInstance), + ModernCleanUpSet = new BasicSimplifyTransformationSet( + Vs16TransformationSet.TargetVisualStudioVersion), + ModernizeSet = new ChainTransformationSet( + new BasicSimplifyTransformationSet(Vs16TransformationSet.TargetVisualStudioVersion), + Vs16ModernizationTransformationSet.TrueInstance), + Diagnostics = diagnostics + }; + + logic.ExecuteWizard(items, conversionOptions, sets); + break; + case "evaluate": + logic.ExecuteEvaluate(items, conversionOptions, Vs16TransformationSet.Instance, new AnalysisOptions(Vs16DiagnosticSet.All)); + break; + case "analyze": + logic.ExecuteAnalyze(items, conversionOptions, new AnalysisOptions(Vs16DiagnosticSet.All)); + break; + case "migrate": + conversionOptions.AppendTargetFrameworkToOutputPath = !command.ValueOrDefault("old-output-path"); + + var forceTransformations = command.ValueOrDefault("force-transformations"); + if (forceTransformations != null) + conversionOptions.ForceDefaultTransforms = forceTransformations; + + logic.ExecuteMigrate(items, command.ValueOrDefault("no-backup"), conversionOptions, Vs16TransformationSet.Instance); + break; + } + + return result.Execute().Code; + } + + private static Command RootCommand() => + Command("dotnet-migrate-2019", + ".NET Project Migration Tool", + NoArguments(), + ProgramBase.Wizard(), + ProgramBase.Evaluate(), + ProgramBase.Migrate(), + ProgramBase.Analyze(), + ProgramBase.HelpOption(), + ProgramBase.VerbosityOption()); + + private static readonly Parser Instance = new Parser(options: RootCommand()); + } +} \ No newline at end of file diff --git a/Project2015To2017.Migrate2019.Tool/Project2015To2017.Migrate2019.Tool.csproj b/Project2015To2017.Migrate2019.Tool/Project2015To2017.Migrate2019.Tool.csproj new file mode 100644 index 0000000..95cb583 --- /dev/null +++ b/Project2015To2017.Migrate2019.Tool/Project2015To2017.Migrate2019.Tool.csproj @@ -0,0 +1,34 @@ + + + + net461;netcoreapp2.1 + netcoreapp2.1 + True + + dotnet-migrate-2019 + Project2015To2017.Migrate2019.Tool + Project2015To2017.Migrate2019.Tool + Exe + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Project2015To2017.MigrateXXXX.Tool/CommandExceptions.cs b/Project2015To2017.MigrateXXXX.Tool/CommandExceptions.cs new file mode 100644 index 0000000..0a4b4d8 --- /dev/null +++ b/Project2015To2017.MigrateXXXX.Tool/CommandExceptions.cs @@ -0,0 +1,24 @@ +using System; + +namespace Project2015To2017.Migrate2017.Tool +{ + /// Allows control flow to be interrupted in order to display help in the console. + internal sealed class HelpException : Exception + { + public HelpException(string message) : base(message) + { + } + } + + internal sealed class CommandParsingException : Exception + { + public CommandParsingException( + string message, + string helpText = null) : base(message) + { + HelpText = helpText ?? ""; + } + + public string HelpText { get; } + } +} \ No newline at end of file diff --git a/Project2015To2017.MigrateXXXX.Tool/CommandLogic.AskBinaryChoice.cs b/Project2015To2017.MigrateXXXX.Tool/CommandLogic.AskBinaryChoice.cs new file mode 100644 index 0000000..7687da6 --- /dev/null +++ b/Project2015To2017.MigrateXXXX.Tool/CommandLogic.AskBinaryChoice.cs @@ -0,0 +1,58 @@ +using System; + +namespace Project2015To2017.Migrate2017.Tool +{ + public partial class CommandLogic + { + private static bool AskBinaryChoice(string question, string yes = "Yes", string no = "No", + bool defaultChoiceIsYes = true) + { + Console.Out.Flush(); + var yesCharLower = char.ToLowerInvariant(yes[0]); + var noCharLower = char.ToLowerInvariant(no[0]); + var yesChar = defaultChoiceIsYes ? char.ToUpperInvariant(yes[0]) : yesCharLower; + var noChar = defaultChoiceIsYes ? noCharLower : char.ToUpperInvariant(no[0]); + Console.Write($"{question} ({yesChar}/{noChar}) "); + Console.Out.Flush(); + bool? result = null; + while (!result.HasValue) + { + result = DetermineKeyChoice(Console.ReadKey(true), yesCharLower, noCharLower, defaultChoiceIsYes); + } + + var realResult = result.Value; + Console.WriteLine(realResult ? yes : no); + Console.Out.Flush(); + return realResult; + } + + private static bool? DetermineKeyChoice(ConsoleKeyInfo info, char yesChar, char noChar, bool defaultChoice) + { + switch (char.ToLowerInvariant(info.KeyChar)) + { + case 'y': + case 't': + case '1': + case char c when c == yesChar: + return true; + case 'n': + case 'f': + case '0': + case char c when c == noChar: + return false; + } + + switch (info.Key) + { + case ConsoleKey.LeftArrow: + return true; + case ConsoleKey.RightArrow: + return false; + case ConsoleKey.Enter: + return defaultChoice; + } + + return null; + } + } +} \ No newline at end of file diff --git a/Project2015To2017.MigrateXXXX.Tool/CommandLogic.Wizard.UnknownTargetFramework.cs b/Project2015To2017.MigrateXXXX.Tool/CommandLogic.Wizard.UnknownTargetFramework.cs new file mode 100644 index 0000000..87a731a --- /dev/null +++ b/Project2015To2017.MigrateXXXX.Tool/CommandLogic.Wizard.UnknownTargetFramework.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Xml; +using System.Xml.Linq; +using Project2015To2017.Definition; +using Serilog; + +namespace Project2015To2017.Migrate2017.Tool +{ + public partial class CommandLogic + { + private readonly List<(ImmutableHashSet<(string when, string tfms)> variant, ImmutableArray answer)> + previousFrameworkResolutionAnswers = + new List<(ImmutableHashSet<(string, string)>, ImmutableArray)>(); + + + private bool shownFrameworkInputExamples = false; + + private bool WizardUnknownTargetFrameworkCallback(Project project, + IReadOnlyList<(IReadOnlyList frameworks, XElement source, string condition)> foundTargetFrameworks) + { + Log.Warning("Cannot unambiguously determine target frameworks for {ProjectName}.", project.FilePath); + + var currentVariant = new HashSet<(string when, string tfms)>(); + + if (foundTargetFrameworks.Count > 0) + { + Log.Information("Found {Count} possible variants:", foundTargetFrameworks.Count); + foreach (var (frameworks, source, condition) in foundTargetFrameworks) + { + if (source is IXmlLineInfo elementOnLine && elementOnLine.HasLineInfo()) + { + Log.Information("{Line}: {TFMs} ({When})", elementOnLine.LineNumber, frameworks, condition); + } + else + { + Log.Information("{TFMs} ({When})", frameworks, condition); + } + + currentVariant.Add((condition, + string.Join(";", frameworks.ToImmutableSortedSet()).ToLowerInvariant())); + } + + foreach (var (variant, answer) in previousFrameworkResolutionAnswers) + { + if (!currentVariant.SetEquals(variant)) + continue; + + Log.Information("You have previously selected {FormerChoice} for that combination.", answer); + if (AskBinaryChoice("Would you like to use this framework set?")) + { + foreach (var tfm in answer) + { + project.TargetFrameworks.Add(tfm); + } + + return true; + } + + break; + } + } + + Log.Information("Please, enter target frameworks to use (comma or space separated):"); + + if (!shownFrameworkInputExamples) + { + Log.Information("e.g.: {Example}", string.Join(" ", "net48", "netstandard2.1", "netcoreapp3.0")); + shownFrameworkInputExamples = true; + } + + Console.Out.Flush(); + + var tfms = Console.ReadLine() + ?.Trim() + .Split(new[] {',', ' '}, StringSplitOptions.RemoveEmptyEntries) + .Where(s => !string.IsNullOrWhiteSpace(s)) + .Select(x => x.Trim()) + .ToImmutableArray() ?? ImmutableArray.Empty; + + if (tfms.IsDefaultOrEmpty) + { + Log.Warning("You didn't specify any TFMs to use."); + return AskBinaryChoice("Attempt to continue without specifying them?", defaultChoiceIsYes: false); + } + + if (foundTargetFrameworks.Count > 0) + { + previousFrameworkResolutionAnswers.Add((currentVariant.ToImmutableHashSet(), tfms)); + } + + foreach (var tfm in tfms) + { + project.TargetFrameworks.Add(tfm); + } + + return true; + } + } +} \ No newline at end of file diff --git a/Project2015To2017.MigrateXXXX.Tool/CommandLogic.WizardMigrate.cs b/Project2015To2017.MigrateXXXX.Tool/CommandLogic.WizardMigrate.cs new file mode 100644 index 0000000..531459b --- /dev/null +++ b/Project2015To2017.MigrateXXXX.Tool/CommandLogic.WizardMigrate.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Project2015To2017.Definition; +using Project2015To2017.Writing; +using Serilog; + +namespace Project2015To2017.Migrate2017.Tool +{ + public partial class CommandLogic + { + private void WizardMigrate(IReadOnlyList legacy, ITransformationSet transformationSet, + ConversionOptions conversionOptions) + { + var transformations = transformationSet.CollectAndOrderTransformations(facility.Logger, conversionOptions); + + var doBackups = AskBinaryChoice("Would you like to create backups?"); + + var writer = new ProjectWriter(facility.Logger, new ProjectWriteOptions {MakeBackups = doBackups}); + + foreach (var project in legacy) + { + using (facility.Logger.BeginScope(project.FilePath)) + { + var projectName = Path.GetFileNameWithoutExtension(project.FilePath.Name); + Log.Information("Converting {ProjectName}...", projectName); + + if (!project.Valid) + { + Log.Error("Project {ProjectName} is marked as invalid, skipping...", projectName); + continue; + } + + foreach (var transformation in transformations.WhereSuitable(project, conversionOptions)) + { + try + { + transformation.Transform(project); + } + catch (Exception e) + { + Log.Error(e, "Transformation {Item} has thrown an exception, skipping...", + transformation.GetType().Name); + } + } + + if (!writer.TryWrite(project)) + continue; + Log.Information("Project {ProjectName} has been converted", projectName); + } + } + } + } +} \ No newline at end of file diff --git a/Project2015To2017.MigrateXXXX.Tool/CommandLogic.WizardModernCleanUp.cs b/Project2015To2017.MigrateXXXX.Tool/CommandLogic.WizardModernCleanUp.cs new file mode 100644 index 0000000..b4898a3 --- /dev/null +++ b/Project2015To2017.MigrateXXXX.Tool/CommandLogic.WizardModernCleanUp.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Project2015To2017.Definition; +using Project2015To2017.Writing; +using Serilog; + +namespace Project2015To2017.Migrate2017.Tool +{ + public partial class CommandLogic + { + private void WizardModernCleanUp(IReadOnlyList modern, ITransformationSet transformationSet, + ConversionOptions conversionOptions) + { + var transformations = transformationSet.CollectAndOrderTransformations(facility.Logger, conversionOptions); + + var writer = new ProjectWriter(facility.Logger, new ProjectWriteOptions()); + + foreach (var project in modern) + { + using (facility.Logger.BeginScope(project.FilePath)) + { + var projectName = Path.GetFileNameWithoutExtension(project.FilePath.Name); + Log.Information("Processing {ProjectName}...", projectName); + + if (!project.Valid) + { + Log.Error("Project {ProjectName} is marked as invalid, skipping...", projectName); + continue; + } + + foreach (var transformation in transformations.WhereSuitable(project, conversionOptions)) + { + try + { + transformation.Transform(project); + } + catch (Exception e) + { + Log.Error(e, "Transformation {Item} has thrown an exception, skipping...", + transformation.GetType().Name); + } + } + + if (!writer.TryWrite(project)) + continue; + Log.Information("Project {ProjectName} has been processed", projectName); + } + } + } + } +} \ No newline at end of file diff --git a/Project2015To2017.MigrateXXXX.Tool/CommandLogic.WizardModernize.cs b/Project2015To2017.MigrateXXXX.Tool/CommandLogic.WizardModernize.cs new file mode 100644 index 0000000..dd22337 --- /dev/null +++ b/Project2015To2017.MigrateXXXX.Tool/CommandLogic.WizardModernize.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Project2015To2017.Definition; +using Project2015To2017.Writing; +using Serilog; + +namespace Project2015To2017.Migrate2017.Tool +{ + public partial class CommandLogic + { + private void WizardModernize(IReadOnlyCollection projects, ITransformationSet transformationSet, + ConversionOptions conversionOptions) + { + var transformations = transformationSet.CollectAndOrderTransformations(facility.Logger, conversionOptions); + + var doBackups = AskBinaryChoice("Would you like to create backups?"); + + var writer = new ProjectWriter(facility.Logger, new ProjectWriteOptions {MakeBackups = doBackups}); + + foreach (var project in projects) + { + using (facility.Logger.BeginScope(project.FilePath)) + { + var projectName = Path.GetFileNameWithoutExtension(project.FilePath.Name); + Log.Information("Modernizing {ProjectName}...", projectName); + + if (!project.Valid) + { + Log.Error("Project {ProjectName} is marked as invalid, skipping...", projectName); + continue; + } + + foreach (var transformation in transformations.WhereSuitable(project, conversionOptions)) + { + try + { + transformation.Transform(project); + } + catch (Exception e) + { + Log.Error(e, "Transformation {Item} has thrown an exception, skipping...", + transformation.GetType().Name); + } + } + + if (!writer.TryWrite(project)) + continue; + Log.Information("Project {ProjectName} has been modernized", projectName); + } + } + } + } +} \ No newline at end of file diff --git a/Project2015To2017.MigrateXXXX.Tool/CommandLogic.cs b/Project2015To2017.MigrateXXXX.Tool/CommandLogic.cs new file mode 100644 index 0000000..ce2164c --- /dev/null +++ b/Project2015To2017.MigrateXXXX.Tool/CommandLogic.cs @@ -0,0 +1,131 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using DotNet.Globbing; +using Project2015To2017.Analysis; +using Project2015To2017.Transforms; +using Project2015To2017.Writing; +using Serilog; + +namespace Project2015To2017.Migrate2017.Tool +{ + public partial class CommandLogic + { + private readonly PatternProcessor globProcessor = (converter, pattern, callback, self) => + { + Log.Verbose("Falling back to globbing"); + self.DoProcessableFileSearch(); + var glob = Glob.Parse(pattern); + Log.Verbose("Parsed glob {Glob}", glob); + foreach (var (path, extension) in self.Files) + { + if (!glob.IsMatch(path)) continue; + var file = new FileInfo(path); + callback(file, extension); + } + + return true; + }; + + private readonly MigrationFacility facility; + + public CommandLogic() + { + var genericLogger = new Serilog.Extensions.Logging.SerilogLoggerProvider().CreateLogger(nameof(Serilog)); + facility = new MigrationFacility(genericLogger, globProcessor); + } + + public void ExecuteEvaluate( + IReadOnlyCollection items, + ConversionOptions conversionOptions, + ITransformationSet transformationSet, + AnalysisOptions analysisOptions) + { + facility.ExecuteEvaluate(items, conversionOptions, transformationSet, analysisOptions); + } + + public void ExecuteMigrate( + IReadOnlyCollection items, + bool noBackup, + ConversionOptions conversionOptions, + ITransformationSet transformationSet) + { + var writeOptions = new ProjectWriteOptions { MakeBackups = !noBackup }; + facility.ExecuteMigrate(items, transformationSet, conversionOptions, writeOptions); + } + + public void ExecuteAnalyze( + IReadOnlyCollection items, + ConversionOptions conversionOptions, + AnalysisOptions analysisOptions) + { + facility.ExecuteAnalyze(items, conversionOptions, analysisOptions); + } + + public void ExecuteWizard( + IReadOnlyCollection items, + ConversionOptions conversionOptions, + WizardTransformationSets sets) + { + if (sets.MigrateSet == null || sets.ModernCleanUpSet == null || sets.ModernizeSet == null) + { + Log.Fatal("Wrong API usage: all transformation sets must be supplied"); + return; + } + + conversionOptions.UnknownTargetFrameworkCallback = WizardUnknownTargetFrameworkCallback; + + var (projects, solutions) = + facility.ParseProjects(items, BasicReadTransformationSet.Instance, conversionOptions); + + if (projects.Count == 0) + { + Log.Information("No projects have been found to match your criteria."); + return; + } + + var (modern, legacy) = projects.Split(x => x.IsModernProject); + foreach (var projectPath in modern.Select(x => x.FilePath)) + { + Log.Information("Project {ProjectPath} is already CPS-based", projectPath); + } + + foreach (var projectPath in solutions.SelectMany(x => x.UnsupportedProjectPaths)) + { + Log.Warning("Project {ProjectPath} migration is not supported at the moment", + projectPath); + } + + facility.DoAnalysis(projects, new AnalysisOptions(DiagnosticSet.All)); + + if (legacy.Count > 0) + { + WizardMigrate(legacy, sets.MigrateSet, conversionOptions); + } + else + { + Log.Information("It appears you already have everything converted to CPS."); + if (AskBinaryChoice("Would you like to process CPS projects to clean up and reformat them?")) + { + WizardModernCleanUp(modern, sets.ModernCleanUpSet, conversionOptions); + } + } + + conversionOptions.ProjectCache?.Purge(); + + (projects, _) = facility.ParseProjects(items, BasicReadTransformationSet.Instance, conversionOptions); + + Log.Information("Modernization can be progressed a little further, but it might lead to unexpected behavioral changes."); + if (AskBinaryChoice("Would you like to modernize projects?")) + { + WizardModernize(projects, sets.ModernizeSet, conversionOptions); + + conversionOptions.ProjectCache?.Purge(); + + (projects, _) = facility.ParseProjects(items, BasicReadTransformationSet.Instance, conversionOptions); + } + + facility.DoAnalysis(projects, new AnalysisOptions(sets.Diagnostics)); + } + } +} \ No newline at end of file diff --git a/Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/Accept.cs b/Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/Accept.cs similarity index 100% rename from Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/Accept.cs rename to Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/Accept.cs diff --git a/Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/AppliedOption.cs b/Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/AppliedOption.cs similarity index 100% rename from Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/AppliedOption.cs rename to Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/AppliedOption.cs diff --git a/Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/AppliedOptionExtensions.cs b/Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/AppliedOptionExtensions.cs similarity index 100% rename from Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/AppliedOptionExtensions.cs rename to Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/AppliedOptionExtensions.cs diff --git a/Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/AppliedOptionSet.cs b/Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/AppliedOptionSet.cs similarity index 100% rename from Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/AppliedOptionSet.cs rename to Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/AppliedOptionSet.cs diff --git a/Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/ArgumentsRule.cs b/Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/ArgumentsRule.cs similarity index 100% rename from Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/ArgumentsRule.cs rename to Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/ArgumentsRule.cs diff --git a/Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/ArgumentsRuleExtensions.cs b/Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/ArgumentsRuleExtensions.cs similarity index 100% rename from Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/ArgumentsRuleExtensions.cs rename to Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/ArgumentsRuleExtensions.cs diff --git a/Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/Command.cs b/Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/Command.cs similarity index 100% rename from Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/Command.cs rename to Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/Command.cs diff --git a/Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/CommandExecutionResult.cs b/Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/CommandExecutionResult.cs similarity index 100% rename from Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/CommandExecutionResult.cs rename to Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/CommandExecutionResult.cs diff --git a/Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/Create.cs b/Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/Create.cs similarity index 100% rename from Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/Create.cs rename to Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/Create.cs diff --git a/Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/DefaultHelpViewText.cs b/Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/DefaultHelpViewText.cs similarity index 100% rename from Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/DefaultHelpViewText.cs rename to Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/DefaultHelpViewText.cs diff --git a/Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/DefaultValidationMessages.cs b/Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/DefaultValidationMessages.cs similarity index 100% rename from Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/DefaultValidationMessages.cs rename to Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/DefaultValidationMessages.cs diff --git a/Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/EnumerableExtensions.cs b/Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/EnumerableExtensions.cs similarity index 100% rename from Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/EnumerableExtensions.cs rename to Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/EnumerableExtensions.cs diff --git a/Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/HelpViewExtensions.cs b/Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/HelpViewExtensions.cs similarity index 100% rename from Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/HelpViewExtensions.cs rename to Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/HelpViewExtensions.cs diff --git a/Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/IValidationMessages.cs b/Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/IValidationMessages.cs similarity index 100% rename from Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/IValidationMessages.cs rename to Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/IValidationMessages.cs diff --git a/Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/Option.cs b/Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/Option.cs similarity index 100% rename from Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/Option.cs rename to Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/Option.cs diff --git a/Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/OptionError.cs b/Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/OptionError.cs similarity index 100% rename from Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/OptionError.cs rename to Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/OptionError.cs diff --git a/Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/OptionExtensions.cs b/Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/OptionExtensions.cs similarity index 100% rename from Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/OptionExtensions.cs rename to Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/OptionExtensions.cs diff --git a/Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/OptionSet.cs b/Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/OptionSet.cs similarity index 100% rename from Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/OptionSet.cs rename to Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/OptionSet.cs diff --git a/Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/OptionSet{T}.cs b/Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/OptionSet{T}.cs similarity index 100% rename from Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/OptionSet{T}.cs rename to Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/OptionSet{T}.cs diff --git a/Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/ParseException.cs b/Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/ParseException.cs similarity index 100% rename from Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/ParseException.cs rename to Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/ParseException.cs diff --git a/Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/ParseResult.cs b/Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/ParseResult.cs similarity index 100% rename from Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/ParseResult.cs rename to Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/ParseResult.cs diff --git a/Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/ParseResultExtensions.cs b/Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/ParseResultExtensions.cs similarity index 100% rename from Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/ParseResultExtensions.cs rename to Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/ParseResultExtensions.cs diff --git a/Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/Parser.cs b/Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/Parser.cs similarity index 100% rename from Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/Parser.cs rename to Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/Parser.cs diff --git a/Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/ParserConfiguration.cs b/Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/ParserConfiguration.cs similarity index 100% rename from Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/ParserConfiguration.cs rename to Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/ParserConfiguration.cs diff --git a/Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/ParserExtensions.cs b/Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/ParserExtensions.cs similarity index 100% rename from Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/ParserExtensions.cs rename to Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/ParserExtensions.cs diff --git a/Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/StringExtensions.cs b/Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/StringExtensions.cs similarity index 100% rename from Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/StringExtensions.cs rename to Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/StringExtensions.cs diff --git a/Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/Suggest.cs b/Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/Suggest.cs similarity index 100% rename from Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/Suggest.cs rename to Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/Suggest.cs diff --git a/Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/Token.cs b/Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/Token.cs similarity index 100% rename from Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/Token.cs rename to Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/Token.cs diff --git a/Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/TokenType.cs b/Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/TokenType.cs similarity index 100% rename from Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/TokenType.cs rename to Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/TokenType.cs diff --git a/Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/ValidationMessages.cs b/Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/ValidationMessages.cs similarity index 100% rename from Project2015To2017.Migrate2017.Tool/Microsoft.DotNet.Cli.CommandLine/ValidationMessages.cs rename to Project2015To2017.MigrateXXXX.Tool/Microsoft.DotNet.Cli.CommandLine/ValidationMessages.cs diff --git a/Project2015To2017.MigrateXXXX.Tool/ProgramBase.ApplyVerbosity.cs b/Project2015To2017.MigrateXXXX.Tool/ProgramBase.ApplyVerbosity.cs new file mode 100644 index 0000000..9a1cd7b --- /dev/null +++ b/Project2015To2017.MigrateXXXX.Tool/ProgramBase.ApplyVerbosity.cs @@ -0,0 +1,41 @@ +using Microsoft.DotNet.Cli.CommandLine; +using Serilog.Events; + +namespace Project2015To2017.Migrate2017.Tool +{ + internal static partial class ProgramBase + { + public static void ApplyVerbosity(ParseResult result, AppliedOption globalOptions) + { + var verbosityValue = globalOptions.ValueOrDefault("verbosity")?.Trim().ToLowerInvariant() ?? + "normal"; + switch (verbosityValue) + { + case "q": + case "quiet": + verbosity.MinimumLevel = LogEventLevel.Fatal + 1; + break; + case "m": + case "minimal": + verbosity.MinimumLevel = LogEventLevel.Warning; + break; + case "n": + case "normal": + verbosity.MinimumLevel = LogEventLevel.Information; + break; + case "d": + case "detailed": + verbosity.MinimumLevel = LogEventLevel.Debug; + break; + // ReSharper disable once StringLiteralTypo + case "diag": + case "diagnostic": + verbosity.MinimumLevel = LogEventLevel.Verbose; + break; + default: + throw new CommandParsingException($"Unknown verbosity level '{verbosityValue}'.", + result.Command().HelpView().TrimEnd()); + } + } + } +} \ No newline at end of file diff --git a/Project2015To2017.MigrateXXXX.Tool/ProgramBase.Common.cs b/Project2015To2017.MigrateXXXX.Tool/ProgramBase.Common.cs new file mode 100644 index 0000000..85fc728 --- /dev/null +++ b/Project2015To2017.MigrateXXXX.Tool/ProgramBase.Common.cs @@ -0,0 +1,20 @@ +using Microsoft.DotNet.Cli.CommandLine; + +namespace Project2015To2017.Migrate2017.Tool +{ + internal static partial class ProgramBase + { + private static ArgumentsRule ItemsArgument => Accept.ZeroOrMoreArguments() + .With("Project/solution file paths or glob patterns", "items") + .DefaultToCurrentDirectory(); + + private static Option TargetFrameworksOption => Create.Option( + "-t|--target-frameworks", + "Override project target frameworks with ones specified. Specify multiple times for multiple target frameworks.", + Accept.OneOrMoreArguments() + .With("Target frameworks to be used instead of the ones in source projects", "frameworks")); + + private static Option KeepAssemblyInfoOption => Create.Option("-a|--keep-assembly-info", + "Keep assembly attributes in AssemblyInfo file instead of moving them to project file."); + } +} \ No newline at end of file diff --git a/Project2015To2017.MigrateXXXX.Tool/ProgramBase.Options.cs b/Project2015To2017.MigrateXXXX.Tool/ProgramBase.Options.cs new file mode 100644 index 0000000..f9f2908 --- /dev/null +++ b/Project2015To2017.MigrateXXXX.Tool/ProgramBase.Options.cs @@ -0,0 +1,83 @@ +using System; +using System.IO; +using System.Linq; +using Microsoft.DotNet.Cli.CommandLine; +using NuGet.Common; +using Serilog; +using Serilog.Core; + +namespace Project2015To2017.Migrate2017.Tool +{ + internal static partial class ProgramBase + { + internal static readonly LoggingLevelSwitch verbosity = new LoggingLevelSwitch(); + + private static ArgumentsRule DefaultToCurrentDirectory(this ArgumentsRule rule) => + rule.With(defaultValue: () => PathUtility.EnsureTrailingSlash(Directory.GetCurrentDirectory())); + + internal static Option ForceOption => Create.Option("-f|--force", + "Force a conversion even if not all preconditions are met."); + + internal static Option HelpOption() => + Create.Option("-h|--help", + "Show help information", + Accept.NoArguments()); + + internal static Option VerbosityOption() => + Create.Option("-v|--verbosity", + // ReSharper disable StringLiteralTypo + "Set the verbosity level of the command. Allowed values are q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic]", + // ReSharper restore StringLiteralTypo + Accept.AnyOneOf("q", "quiet", + "m", "minimal", + "n", "normal", + "d", "detailed", + // ReSharper disable once StringLiteralTypo + "diag", "diagnostic") + .With(name: "LEVEL")); + + internal static void ShowHelpOrErrorIfAppropriate(this ParseResult parseResult) + { + parseResult.ShowHelpIfRequested(); + + if (parseResult.Errors.Any()) + { + throw new CommandParsingException( + String.Join(Environment.NewLine, + parseResult.Errors.Select(e => e.Message)), + parseResult.Command()?.HelpView().TrimEnd()); + } + } + + private static void ShowHelpIfRequested(this ParseResult parseResult) + { + var appliedCommand = parseResult.AppliedCommand(); + + if (appliedCommand.HasOption("help") || + appliedCommand.Arguments.Contains("-?") || + appliedCommand.Arguments.Contains("/?")) + { + throw new HelpException(parseResult.Command().HelpView().TrimEnd()); + } + } + + public static T ValueOrDefault(this AppliedOption parseResult, string alias) + { + return parseResult + .AppliedOptions + .Where(o => o.HasAlias(alias)) + .Select(o => o.Value()) + .SingleOrDefault(); + } + + internal static void CreateLogger() + { + Log.Logger = new LoggerConfiguration() + .Enrich.FromLogContext() + .Enrich.WithDemystifiedStackTraces() + .MinimumLevel.ControlledBy(ProgramBase.verbosity) + .WriteTo.Console() + .CreateLogger(); + } + } +} \ No newline at end of file diff --git a/Project2015To2017.MigrateXXXX.Tool/ProgramBase.PrimaryCommands.cs b/Project2015To2017.MigrateXXXX.Tool/ProgramBase.PrimaryCommands.cs new file mode 100644 index 0000000..731d5c3 --- /dev/null +++ b/Project2015To2017.MigrateXXXX.Tool/ProgramBase.PrimaryCommands.cs @@ -0,0 +1,47 @@ +using Microsoft.DotNet.Cli.CommandLine; + +namespace Project2015To2017.Migrate2017.Tool +{ + internal static partial class ProgramBase + { + internal static Command Evaluate() => + Create.Command("evaluate", + "Examine the projects potential to be converted before actual migration", + ItemsArgument, + TargetFrameworksOption, + HelpOption()); + + internal static Command Migrate() => + Create.Command("migrate", + "Migrate projects to modern Visual Studio CPS format (non-interactive)", + ItemsArgument, + Create.Option("-n|--no-backup", + "Skip moving project.json, global.json, and *.xproj to a `Backup` directory after successful migration."), + ForceOption, + KeepAssemblyInfoOption, + TargetFrameworksOption, + Create.Option("-o|--old-output-path", + "Preserve legacy behavior by not creating a subfolder with the target framework in the output path."), + Create.Option( + "-ft|--force-transformations", + "Force execution of transformations despite project conversion state by their specified names. " + + "Specify multiple times for multiple enforced transformations.", + Accept.OneOrMoreArguments() + .With("Transformation names to enforce execution", "names")), + HelpOption()); + + internal static Command Analyze() => + Create.Command("analyze", + "Do the analysis run and output diagnostics", + ItemsArgument, + HelpOption()); + + internal static Command Wizard() => + Create.Command("wizard", + "Launch interactive migration wizard (recommended)", + ItemsArgument, + ForceOption, + KeepAssemblyInfoOption, + HelpOption()); + } +} \ No newline at end of file diff --git a/Project2015To2017.MigrateXXXX.Tool/Project2015To2017.MigrateXXXX.Tool.proj b/Project2015To2017.MigrateXXXX.Tool/Project2015To2017.MigrateXXXX.Tool.proj new file mode 100644 index 0000000..0744f3a --- /dev/null +++ b/Project2015To2017.MigrateXXXX.Tool/Project2015To2017.MigrateXXXX.Tool.proj @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Project2015To2017.MigrateXXXX.Tool/WizardTransformationSets.cs b/Project2015To2017.MigrateXXXX.Tool/WizardTransformationSets.cs new file mode 100644 index 0000000..e1994d4 --- /dev/null +++ b/Project2015To2017.MigrateXXXX.Tool/WizardTransformationSets.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Project2015To2017.Analysis; + +namespace Project2015To2017.Migrate2017.Tool +{ + public sealed class WizardTransformationSets + { + public ITransformationSet MigrateSet { get; set; } + public ITransformationSet ModernCleanUpSet { get; set; } + public ITransformationSet ModernizeSet { get; set; } + public HashSet Diagnostics { get; set; } + } +} diff --git a/Project2015To2017.Tests/PropertySimplificationTransformationTest.cs b/Project2015To2017.Tests/PropertySimplificationTransformationTest.cs index e9c0e41..394c411 100644 --- a/Project2015To2017.Tests/PropertySimplificationTransformationTest.cs +++ b/Project2015To2017.Tests/PropertySimplificationTransformationTest.cs @@ -365,99 +365,6 @@ public void RemovesVisualStudioVersion() Assert.IsTrue(!project.ProjectDocument.Descendants().Any(x => x.Name.LocalName == "VisualStudioVersion")); } - [TestMethod] - public void RemovesServiceTagWithKnownTestFramework() - { - var guid = Guid.NewGuid(); - var xml = @" - - - Debug - AnyCPU - {" + guid.ToString() + @"} - Library - Properties - ClassLibrary1 - ClassLibrary1 - v4.6.1 - 512 - SAK - SAK - SAK - SAK - 10.0 - - - - - - "; - - var project = ParseAndTransform(xml, projectName: "Class1"); - var name = "someproject"; - project.ProjectName = name; - project.Solution = new Solution - { - ProjectPaths = new[] - { - new ProjectReference - { - ProjectName = name, - ProjectGuid = guid - } - } - }; - - new PropertySimplificationTransformation().Transform(project); - - Assert.IsFalse(project.ProjectDocument.Descendants().Any(x => x.Name.LocalName == "Service")); - } - - [TestMethod] - public void DoesNotRemoveServiceTag() - { - var guid = Guid.NewGuid(); - var xml = @" - - - Debug - AnyCPU - {" + guid.ToString() + @"} - Library - Properties - ClassLibrary1 - ClassLibrary1 - v4.6.1 - 512 - SAK - SAK - SAK - SAK - 10.0 - - - "; - - var project = ParseAndTransform(xml, projectName: "Class1"); - var name = "someproject"; - project.ProjectName = name; - project.Solution = new Solution - { - ProjectPaths = new[] - { - new ProjectReference - { - ProjectName = name, - ProjectGuid = guid - } - } - }; - - new PropertySimplificationTransformation().Transform(project); - - Assert.IsTrue(project.ProjectDocument.Descendants().Any(x => x.Name.LocalName == "Service")); - } - private static Project ParseAndTransform( string xml, [System.Runtime.CompilerServices.CallerMemberName] diff --git a/Project2015To2017.Tests/ServiceFilterTransformationTest.cs b/Project2015To2017.Tests/ServiceFilterTransformationTest.cs new file mode 100644 index 0000000..bbaf0f3 --- /dev/null +++ b/Project2015To2017.Tests/ServiceFilterTransformationTest.cs @@ -0,0 +1,188 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Project2015To2017.Definition; +using Project2015To2017.Reading; +using Project2015To2017.Transforms; + +namespace Project2015To2017.Tests +{ + [TestClass] + public class ServiceFilterTransformationTest + { + [DataRow(0, 0)] + [DataRow(15, 0)] + [DataRow(15, 6)] + [DataRow(15, 7)] + [DataRow(16, 0)] + [TestMethod] + public void RemovesServiceTagWithKnownTestFramework(int visualStudioVersionMajor, int visualStudioVersionMinor) + { + var guid = Guid.NewGuid(); + var xml = @" + + + Debug + AnyCPU + {" + guid.ToString() + @"} + Library + Properties + ClassLibrary1 + ClassLibrary1 + v4.6.1 + 512 + SAK + SAK + SAK + SAK + 10.0 + + + + + + "; + + var project = ParseAndTransform(xml, projectName: "Class1"); + var name = "someproject"; + project.ProjectName = name; + project.Solution = new Solution + { + ProjectPaths = new[] + { + new ProjectReference + { + ProjectName = name, + ProjectGuid = guid + } + } + }; + + new ServiceFilterTransformation(new Version(visualStudioVersionMajor, visualStudioVersionMinor)).Transform(project); + + Assert.IsFalse(project.ProjectDocument.Descendants().Any(x => x.Name.LocalName == "Service")); + } + + [DataRow(0, 0, true)] + [DataRow(15, 0, true)] + [DataRow(15, 6, true)] + [DataRow(15, 7, false)] + [DataRow(16, 0, false)] + [TestMethod] + public void RemovesServiceTagWithVisualStudioVersion(int visualStudioVersionMajor, int visualStudioVersionMinor, bool expected) + { + var guid = Guid.NewGuid(); + var xml = @" + + + Debug + AnyCPU + {" + guid.ToString() + @"} + Library + Properties + ClassLibrary1 + ClassLibrary1 + v4.6.1 + 512 + SAK + SAK + SAK + SAK + 10.0 + + + + + "; + + var project = ParseAndTransform(xml, projectName: "Class1"); + var name = "someproject"; + project.ProjectName = name; + project.Solution = new Solution + { + ProjectPaths = new[] + { + new ProjectReference + { + ProjectName = name, + ProjectGuid = guid + } + } + }; + + new ServiceFilterTransformation(new Version(visualStudioVersionMajor, visualStudioVersionMinor)).Transform(project); + + Assert.AreEqual(expected, project.ProjectDocument.Descendants().Any(x => x.Name.LocalName == "Service")); + } + + [DataRow(0, 0)] + [DataRow(15, 0)] + [DataRow(15, 6)] + [DataRow(15, 7)] + [DataRow(16, 0)] + [TestMethod] + public void DoesNotRemoveServiceTag(int visualStudioVersionMajor, int visualStudioVersionMinor) + { + var guid = Guid.NewGuid(); + var xml = @" + + + Debug + AnyCPU + {" + guid.ToString() + @"} + Library + Properties + ClassLibrary1 + ClassLibrary1 + v4.6.1 + 512 + SAK + SAK + SAK + SAK + 10.0 + + + "; + + var project = ParseAndTransform(xml, projectName: "Class1"); + var name = "someproject"; + project.ProjectName = name; + project.Solution = new Solution + { + ProjectPaths = new[] + { + new ProjectReference + { + ProjectName = name, + ProjectGuid = guid + } + } + }; + + new ServiceFilterTransformation(new Version(visualStudioVersionMajor, visualStudioVersionMinor)).Transform(project); + + Assert.IsTrue(project.ProjectDocument.Descendants().Any(x => x.Name.LocalName == "Service")); + } + + private static Project ParseAndTransform( + string xml, + [System.Runtime.CompilerServices.CallerMemberName] + string memberName = "", + string projectName = null + ) + { + var testCsProjFile = $"{memberName}_test.csproj"; + + File.WriteAllText(testCsProjFile, xml, Encoding.UTF8); + + var project = new ProjectReader().Read(testCsProjFile); + project.ProjectName = projectName; + project.FilePath = null; + + return project; + } + } +} \ No newline at end of file diff --git a/Project2015To2017.sln b/Project2015To2017.sln index 69f407e..6261156 100644 --- a/Project2015To2017.sln +++ b/Project2015To2017.sln @@ -25,6 +25,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Project2015To2017.Migrate20 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Project2015To2017.Migrate2019.Library", "Project2015To2017.Migrate2019.Library\Project2015To2017.Migrate2019.Library.csproj", "{69EB7F70-ACFD-43F7-829E-2FC340A27E41}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Project2015To2017.Migrate2019.Tool", "Project2015To2017.Migrate2019.Tool\Project2015To2017.Migrate2019.Tool.csproj", "{9B6CF418-64C3-4630-9838-5605EFAA5822}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -59,6 +61,10 @@ Global {69EB7F70-ACFD-43F7-829E-2FC340A27E41}.Debug|Any CPU.Build.0 = Debug|Any CPU {69EB7F70-ACFD-43F7-829E-2FC340A27E41}.Release|Any CPU.ActiveCfg = Release|Any CPU {69EB7F70-ACFD-43F7-829E-2FC340A27E41}.Release|Any CPU.Build.0 = Release|Any CPU + {9B6CF418-64C3-4630-9838-5605EFAA5822}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9B6CF418-64C3-4630-9838-5605EFAA5822}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9B6CF418-64C3-4630-9838-5605EFAA5822}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9B6CF418-64C3-4630-9838-5605EFAA5822}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Project2015To2017/MigrationFacility.cs b/Project2015To2017/MigrationFacility.cs index 0b61c9a..0900996 100644 --- a/Project2015To2017/MigrationFacility.cs +++ b/Project2015To2017/MigrationFacility.cs @@ -139,7 +139,7 @@ public void DoAnalysis(IEnumerable projects, AnalysisOptions options = } } - return (convertedProjects, convertedSolutions); + return (convertedProjects.Distinct(Project.ProjectNameFilePathComparer).ToArray(), convertedSolutions.Distinct(Solution.FilePathComparer).ToArray()); void ProcessSingleItem(FileInfo file, string extension) { @@ -176,9 +176,31 @@ void ProcessSingleItem(FileInfo file, string extension) public void ExecuteEvaluate( IReadOnlyCollection items, - ConversionOptions conversionOptions) + ConversionOptions conversionOptions = null, + ITransformationSet transformationSet = null, + AnalysisOptions analysisOptions = null) { - var (projects, solutions) = ParseProjects(items, Vs15TransformationSet.Instance, conversionOptions); + if (transformationSet != null && analysisOptions == null) + { + Logger.LogWarning("You should pass AnalysisOptions if you pass ITransformationSet, falling back to using default diagnostic set"); + } + + if (transformationSet == null && analysisOptions == null) + { + Logger.LogDebug("Legacy signature usage, please specify all options instead"); + analysisOptions = new AnalysisOptions(Vs15DiagnosticSet.All); + } + + if (transformationSet == null) + { + Logger.LogDebug("Using default VS2015 migration transformation set, please specify the one you need instead"); + transformationSet = Vs15TransformationSet.Instance; + } + + conversionOptions = conversionOptions ?? new ConversionOptions(); + analysisOptions = analysisOptions ?? new AnalysisOptions(); + + var (projects, solutions) = ParseProjects(items, transformationSet, conversionOptions); if (projects.Count == 0) { @@ -187,7 +209,7 @@ public void ExecuteEvaluate( var alreadyConverted = projects.Where(x => x.IsModernProject).ToImmutableArray(); - DoAnalysis(projects, new AnalysisOptions(Vs15DiagnosticSet.All)); + DoAnalysis(projects, analysisOptions); Logger.LogInformation("List of modern CPS projects:"); foreach (var projectPath in alreadyConverted.Select(x => x.FilePath)) @@ -205,19 +227,11 @@ public void ExecuteEvaluate( } } - public void ExecuteMigrate( - IReadOnlyCollection items, - ITransformationSet transformations - ) - { - ExecuteMigrate(items, transformations, new ConversionOptions(), new ProjectWriteOptions()); - } - + [Obsolete] public void ExecuteMigrate( IReadOnlyCollection items, ConversionOptions conversionOptions, - ProjectWriteOptions writeOptions - ) + ProjectWriteOptions writeOptions) { ExecuteMigrate(items, Vs15TransformationSet.Instance, conversionOptions, writeOptions); } @@ -225,10 +239,13 @@ ProjectWriteOptions writeOptions public void ExecuteMigrate( IReadOnlyCollection items, ITransformationSet transformations, - ConversionOptions conversionOptions, - ProjectWriteOptions writeOptions - ) + ConversionOptions conversionOptions = null, + ProjectWriteOptions writeOptions = null, + AnalysisOptions analysisOptions = null) { + conversionOptions = conversionOptions ?? new ConversionOptions(); + writeOptions = writeOptions ?? new ProjectWriteOptions(); + var (projects, _) = ParseProjects(items, transformations, conversionOptions); if (projects.Count == 0) @@ -246,12 +263,13 @@ ProjectWriteOptions writeOptions (projects, _) = ParseProjects(items, BasicReadTransformationSet.Instance, conversionOptions); - DoAnalysis(projects); + DoAnalysis(projects, analysisOptions); } public void ExecuteAnalyze( IReadOnlyCollection items, - ConversionOptions conversionOptions) + ConversionOptions conversionOptions, + AnalysisOptions analysisOptions = null) { var (projects, _) = ParseProjects(items, BasicReadTransformationSet.Instance, conversionOptions); @@ -260,7 +278,7 @@ public void ExecuteAnalyze( return; } - DoAnalysis(projects); + DoAnalysis(projects, analysisOptions); } } } \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index 2ee1e41..b3c72b3 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 4.0.{build} +version: 4.1.{build} skip_tags: true image: - Visual Studio 2019 Preview @@ -10,67 +10,72 @@ skip_branch_with_pr: true environment: LOGGER: '/l:"C:\Program Files\AppVeyor\BuildAgent\dotnetcore\Appveyor.MSBuildLogger.dll"' LIBRARY: './Project2015To2017/Project2015To2017.csproj' - LEGACYCLITOOL: './Project2015To2017.Console/Project2015To2017.Console.csproj' - CLITOOL: './Project2015To2017.Migrate2017.Tool/Project2015To2017.Migrate2017.Tool.csproj' + CLITOOL2017: './Project2015To2017.Migrate2017.Tool/Project2015To2017.Migrate2017.Tool.csproj' + CLITOOL2019: './Project2015To2017.Migrate2019.Tool/Project2015To2017.Migrate2019.Tool.csproj' build_script: - cmd: dotnet --version - cmd: dotnet pack %LIBRARY% %LOGGER% -v m -c %configuration% --force /p:Pack=true - - cmd: dotnet pack %LEGACYCLITOOL% %LOGGER% -v m -c %configuration% --force /p:Pack=true - - cmd: dotnet pack %CLITOOL% %LOGGER% -v m -c %configuration% --force /p:Pack=true - - cmd: dotnet publish %CLITOOL% %LOGGER% -v m -c %configuration% -f netcoreapp2.1 -o ./out/netcoreapp2.1 - - cmd: dotnet publish %CLITOOL% %LOGGER% -v m -c %configuration% -f net461 -o ./out/net461 + - cmd: dotnet pack %CLITOOL2017% %LOGGER% -v m -c %configuration% --force /p:Pack=true + - cmd: dotnet publish %CLITOOL2017% %LOGGER% -v m -c %configuration% -f netcoreapp2.1 -o ./out2017/netcoreapp2.1 + - cmd: dotnet publish %CLITOOL2017% %LOGGER% -v m -c %configuration% -f net461 -o ./out2017/net461 + - cmd: dotnet pack %CLITOOL2019% %LOGGER% -v m -c %configuration% --force /p:Pack=true + - cmd: dotnet publish %CLITOOL2019% %LOGGER% -v m -c %configuration% -f netcoreapp2.1 -o ./out2019/netcoreapp2.1 + - cmd: dotnet publish %CLITOOL2019% %LOGGER% -v m -c %configuration% -f net461 -o ./out2019/net461 after_build: - - cmd: 7z a ./dotnet-migrate-2017.zip ./out/* + - cmd: 7z a ./dotnet-migrate-2017.zip ./out2017/* + - cmd: 7z a ./dotnet-migrate-2019.zip ./out2019/* test_script: - cmd: dotnet test Project2015To2017.Tests/Project2015To2017.Tests.csproj -c %configuration% --test-adapter-path:. --logger:Appveyor artifacts: - - path: ./Project2015To2017.Console/bin/$(configuration)/Project2015To2017.Cli.**.nupkg - name: global-tool-legacy - path: ./Project2015To2017.Migrate2017.Tool/bin/$(configuration)/Project2015To2017.Migrate2017.Tool.**.nupkg - name: global-tool + name: global-tool-2017 + - path: ./Project2015To2017.Migrate2019.Tool/bin/$(configuration)/Project2015To2017.Migrate2019.Tool.**.nupkg + name: global-tool-2019 - path: ./dotnet-migrate-2017.zip - name: out-zip + name: out-zip-2017 + - path: ./dotnet-migrate-2019.zip + name: out-zip-2019 - path: Project2015To2017\bin\$(configuration)\Project2015To2017.**.nupkg name: nupkg - + deploy: - provider: NuGet api_key: secure: BzG/1VAXhFOoaGkbPFAKE1/D2jV7fbwBoXe2wCJsj5jtIANB1THXwOIcFGmNaQtH skip_symbols: false artifact: nupkg - + on: + branch: master + - provider: NuGet api_key: - secure: AwfCakaWfhBSFa7M+QoESC9wuzZSDQ5/hn3Rpgj7b5nPM1zm6ge6crkvbW7L6i7U + secure: ONRE7O/9v0W/ZyO0EwYRW7BOFZhVV9h8pxGtaFCO7aNglrElUGZ+MEFbDh0j5A9S skip_symbols: false - artifact: global-tool-legacy - + artifact: global-tool-2017 + on: + branch: master + - provider: NuGet api_key: - secure: ONRE7O/9v0W/ZyO0EwYRW7BOFZhVV9h8pxGtaFCO7aNglrElUGZ+MEFbDh0j5A9S + secure: BmoLFLk70U1TUsSiscVfwGFaT8v8N4G9eJNCnPQRvFoXiGKYed6IxYaN6eRr28t3 skip_symbols: false - artifact: global-tool - -#- provider: NuGet -# api_key: -# secure: BmoLFLk70U1TUsSiscVfwGFaT8v8N4G9eJNCnPQRvFoXiGKYed6IxYaN6eRr28t3 -# skip_symbols: false -# artifact: global-tool-2019 + artifact: global-tool-2019 + on: + branch: master - provider: GitHub - release: dotnet-migrate-2017-v$(APPVEYOR_BUILD_VERSION) + release: dotnet-migrate-v$(APPVEYOR_BUILD_VERSION) description: 'Automatically built release v$(APPVEYOR_BUILD_VERSION).' auth_token: secure: "LYFGDMnT4oJLpCsZUkhL/oTnG3ZpQnsjaTc10DfTf/jbkXtzctRkXEu2ea3YnyqP" - artifact: out-zip + artifact: out-zip-2017,out-zip-2019 draft: true prerelease: true on: - branch: master # deploy on tag push only + branch: master # build cache to preserve files/folders between builds cache: - packages -> **\packages.config # preserve "packages" directory in the root of build folder but will reset it if packages.config is modified - - '%LocalAppData%\NuGet\Cache' + - '%LocalAppData%\NuGet\Cache' \ No newline at end of file