From f1b0ee47b2698f7d8edb737db698e1ebed00bbd7 Mon Sep 17 00:00:00 2001 From: Deep Choudhery <54324771+deepchoudhery@users.noreply.github.com> Date: Tue, 13 Sep 2022 14:41:31 -0700 Subject: [PATCH] [MsIdentity] parse csproj file and add tfm(s). (#2016) * parse csproj file and add tfm(s). * removing empty test. --- scripts/install-msidentity.cmd | 2 +- .../CodeReaderWriter/ProjectModifier.cs | 4 +- .../Tool/ProvisioningToolOptions.cs | 6 + .../Project/ProjectModifierHelper.cs | 100 +++++++++++++++++ .../ProjectModel/ProjectModelHelper.cs | 2 +- .../DocumentBuilderTestBase.cs | 105 ++++++++++++++++++ .../ProjectModifierHelperTests.cs | 27 +++++ 7 files changed, 243 insertions(+), 3 deletions(-) diff --git a/scripts/install-msidentity.cmd b/scripts/install-msidentity.cmd index f9792c06f..0b0b98614 100644 --- a/scripts/install-msidentity.cmd +++ b/scripts/install-msidentity.cmd @@ -1,4 +1,4 @@ -set VERSION=1.0.0-dev +set VERSION=2.0.0-dev set NUPKG=artifacts\packages\Debug\Shipping\ pushd %~dp0 diff --git a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/CodeReaderWriter/ProjectModifier.cs b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/CodeReaderWriter/ProjectModifier.cs index c118ec224..9aa29a47b 100644 --- a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/CodeReaderWriter/ProjectModifier.cs +++ b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/CodeReaderWriter/ProjectModifier.cs @@ -53,9 +53,11 @@ public async Task AddAuthCodeAsync() _consoleLogger.LogJsonMessage(new JsonResponse(Commands.UPDATE_PROJECT_COMMAND, State.Fail, output: errorMsg)); return; } - + _toolOptions.ProjectFilePath = csProjFiles.First(); } + string csprojText = File.ReadAllText(_toolOptions.ProjectFilePath); + _toolOptions.ShortTfms = ProjectModifierHelper.ProcessCsprojFile(csprojText); CodeModifierConfig? codeModifierConfig = GetCodeModifierConfig(); if (codeModifierConfig is null || !codeModifierConfig.Files.Any()) diff --git a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/ProvisioningToolOptions.cs b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/ProvisioningToolOptions.cs index a44f20fe4..2c1d8c88e 100644 --- a/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/ProvisioningToolOptions.cs +++ b/src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/ProvisioningToolOptions.cs @@ -14,6 +14,12 @@ public class ProvisioningToolOptions : IDeveloperCredentialsOptions /// public string? ProjectFilePath { get; set; } + /// + /// Short target framework from the given ProjectFilePath. List to allow multiple tfms. + /// eg. net6.0, net7.0 etc. + /// + public IList ShortTfms { get; set; } = new List(); + /// /// Path to appsettings.json file /// diff --git a/src/Shared/Microsoft.DotNet.Scaffolding.Shared/Project/ProjectModifierHelper.cs b/src/Shared/Microsoft.DotNet.Scaffolding.Shared/Project/ProjectModifierHelper.cs index 5714c71b7..dba14a1ff 100644 --- a/src/Shared/Microsoft.DotNet.Scaffolding.Shared/Project/ProjectModifierHelper.cs +++ b/src/Shared/Microsoft.DotNet.Scaffolding.Shared/Project/ProjectModifierHelper.cs @@ -5,11 +5,13 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; +using System.Xml.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using Microsoft.DotNet.Scaffolding.Shared.CodeModifier; using Microsoft.DotNet.Scaffolding.Shared.CodeModifier.CodeChange; +using Microsoft.DotNet.Scaffolding.Shared.ProjectModel; namespace Microsoft.DotNet.Scaffolding.Shared.Project { @@ -84,6 +86,64 @@ internal static async Task IsUsingTopLevelStatements(IModelTypesLocator mo return true; } + /// + /// Parses the csproj xml text and gets one or more TargetFrameworks for the project. + /// + /// .csproj file as string + /// string[] containing target frameworks of the project + internal static string[] ProcessCsprojFile(string csprojText) + { + List processedTfms = new List(); + if (!string.IsNullOrEmpty(csprojText)) + { + //use XDocument to get all csproj elements. + XDocument document = XDocument.Parse(csprojText); + var docNodes = document.Root?.Elements(); + var allElements = docNodes?.SelectMany(x => x.Elements()); + //add them to a dictionary for easy comparisons. + var csprojVariables = new Dictionary(StringComparer.OrdinalIgnoreCase); + if (allElements != null && allElements.Any()) + { + foreach (var elem in allElements) + { + //dont' add PackageReference(s) since they are useless for getting tfm properties. + if (!elem.Name.LocalName.Equals("PackageReference", StringComparison.OrdinalIgnoreCase)) + { + //change the keys from TargetFramework to $(TargetFramework) and so forth for nested variable analysis. + //eg. analysing $(X) and getting the value for $(X). + //makes for a easy string comparison without using regex and splitting. + csprojVariables.TryAdd(string.Format("$({0})", elem.Name.LocalName), elem.Value); + } + } + } + + //if only one TargetFramework + if (csprojVariables.TryGetValue("$(TargetFramework)", out string tfmValue)) + { + string processedTfm = ProcessTfm(tfmValue.Trim(), csprojVariables); + if (!string.IsNullOrEmpty(processedTfm) && ProjectModelHelper.ShortTfmDictionary.Values.ToList().Contains(processedTfm, StringComparer.OrdinalIgnoreCase)) + { + processedTfms.Add(processedTfm); + } + } + //if multiple, split by ';' and add them all. + else if (csprojVariables.TryGetValue("$(TargetFrameworks)", out string tfms)) + { + string processedTfm = ProcessTfm(tfms.Trim(), csprojVariables); + //tfms should be separated by ; + var splitTfms = processedTfm.Split(";"); + foreach (var tfm in splitTfms) + { + if (!string.IsNullOrEmpty(tfm) && ProjectModelHelper.ShortTfmDictionary.Values.ToList().Contains(tfm, StringComparer.OrdinalIgnoreCase)) + { + processedTfms.Add(tfm); + } + } + } + } + return processedTfms.ToArray(); + } + // Returns true when there is no Startup.cs or equivalent internal static async Task IsMinimalApp(List documents) { @@ -522,5 +582,45 @@ internal static CodeBlock[] FilterCodeBlocks(CodeBlock[] codeBlocks, CodeChangeO } return filteredCodeBlocks.ToArray(); } + + /// + /// Take the tfm value in the csproj and use the Dictionary of variables to find its true value. + /// + /// value for or ' in the csproj file + /// dictionary with all csproj properties and values + /// + internal static string ProcessTfm(string tfm, Dictionary csprojVariables) + { + if (string.IsNullOrEmpty(tfm)) + { + return string.Empty; + } + + bool tfmHasVars = true; + while (tfmHasVars) + { + //if the value is in the tfm dictionary (valid values), return it. + if (ProjectModelHelper.ShortTfmDictionary.Values.ToList().Contains(tfm, StringComparer.OrdinalIgnoreCase)) + { + return tfm; + } + //if the value has a variable (key) in it, replace it with its value. + else if (tfm.Contains('$')) + { + foreach (var key in csprojVariables.Keys) + { + if (tfm.Contains(key, StringComparison.OrdinalIgnoreCase) && csprojVariables.TryGetValue(key, out string val)) + { + tfm = tfm.Replace(key, val); + } + } + } + else + { + return tfm; + } + } + return tfm; + } } } diff --git a/src/Shared/Microsoft.DotNet.Scaffolding.Shared/ProjectModel/ProjectModelHelper.cs b/src/Shared/Microsoft.DotNet.Scaffolding.Shared/ProjectModel/ProjectModelHelper.cs index 8e72eada4..43ba95d4b 100644 --- a/src/Shared/Microsoft.DotNet.Scaffolding.Shared/ProjectModel/ProjectModelHelper.cs +++ b/src/Shared/Microsoft.DotNet.Scaffolding.Shared/ProjectModel/ProjectModelHelper.cs @@ -27,7 +27,7 @@ public static string GetProjectAssetsFile(IProjectContext projectInformation) return string.Empty; } - private static Dictionary ShortTfmDictionary = new Dictionary() + internal static Dictionary ShortTfmDictionary = new Dictionary() { { ".NETCoreApp,Version=v3.1", "netcoreapp3.1" }, { ".NETCoreApp,Version=v5.0", "net5.0" }, diff --git a/test/Shared/Microsoft.DotNet.Scaffolding.Shared.Tests/DocumentBuilderTestBase.cs b/test/Shared/Microsoft.DotNet.Scaffolding.Shared.Tests/DocumentBuilderTestBase.cs index 5ee8fcfed..0e8bdf0e1 100644 --- a/test/Shared/Microsoft.DotNet.Scaffolding.Shared.Tests/DocumentBuilderTestBase.cs +++ b/test/Shared/Microsoft.DotNet.Scaffolding.Shared.Tests/DocumentBuilderTestBase.cs @@ -118,6 +118,111 @@ protected static List CreateParameterList(string[] types, strin return paramList; } + protected const string Net7Csproj = @" + + net7.0 + enable + enable + + + +"; + + protected const string Net7CsprojVariabledCsproj = @" + + $(Var2)$(Var3) + net + 7.0 + $(Var1) + enable + enable + + + +"; + + protected const string Net7CsprojVariabledCsproj2 = @" + + net7.0 + $(Var1) + enable + enable + + +"; + + protected const string MultiTfmCsproj = @" + + + net6.0;net7.0 + enable + enable + + + +"; + + protected const string EmptyCsproj = @" + +"; + protected const string EmptyCsproj2 = @""; + + protected const string MultiTfmVariabledCsproj = @" + + $(Var2);$(Var3) + net7.0 + enable + net6.0 + $(Var1) + enable + + + +"; + protected const string MultiTfmVariabledCsproj2 = @" + + $(Var2)$(Var3) + net + 7.0 + enable + net6.0 + $(Var1);$(Var4);net5.0 + enable + + + +"; + protected const string InvalidCsproj = @" + + net69.0 + enable + enable + + + +"; + protected const string InvalidCsproj2 = @" + + $(Var2);$(Var3) + net77.0 + enable + net69.0 + $(Var1) + enable + + + +"; + protected const string InvalidCsproj3 = @" + + net77.0 + $(Var1) + enable + enable + + +"; + protected const string FullDocument = @" using System; using System.Duplicate; diff --git a/test/Shared/Microsoft.DotNet.Scaffolding.Shared.Tests/ProjectModifierHelperTests.cs b/test/Shared/Microsoft.DotNet.Scaffolding.Shared.Tests/ProjectModifierHelperTests.cs index ab5efb63a..2ff4a4e67 100644 --- a/test/Shared/Microsoft.DotNet.Scaffolding.Shared.Tests/ProjectModifierHelperTests.cs +++ b/test/Shared/Microsoft.DotNet.Scaffolding.Shared.Tests/ProjectModifierHelperTests.cs @@ -497,6 +497,33 @@ public async Task GlobalStatementExistsTests(string[] existingStatements, string } } + [Fact] + public void ProcessCsprojFile() + { + var net7Tfms = ProjectModifierHelper.ProcessCsprojFile(Net7Csproj); + var net7Tfms2 = ProjectModifierHelper.ProcessCsprojFile(Net7CsprojVariabledCsproj); + var net7Tfms3 = ProjectModifierHelper.ProcessCsprojFile(Net7CsprojVariabledCsproj2); + Assert.True(net7Tfms.Length == 1 && net7Tfms2.Length == 1 && net7Tfms3.Length == 1); + Assert.True(net7Tfms.First().Equals("net7.0") && net7Tfms2.First().Equals("net7.0") && net7Tfms3.First().Equals("net7.0")); + + var emptyTfms = ProjectModifierHelper.ProcessCsprojFile(EmptyCsproj); + var emptyTfms2 = ProjectModifierHelper.ProcessCsprojFile(EmptyCsproj2); + Assert.True(emptyTfms.Length == 0 && emptyTfms2.Length == 0); + + var invalidTfm = ProjectModifierHelper.ProcessCsprojFile(InvalidCsproj); + var invalidTfm2 = ProjectModifierHelper.ProcessCsprojFile(InvalidCsproj2); + var invalidTfm3 = ProjectModifierHelper.ProcessCsprojFile(InvalidCsproj3); + Assert.True(invalidTfm.Length == 0 && invalidTfm2.Length == 0 && invalidTfm3.Length == 0); + + var multiTfm = ProjectModifierHelper.ProcessCsprojFile(MultiTfmCsproj); + var multiTfm2 = ProjectModifierHelper.ProcessCsprojFile(MultiTfmVariabledCsproj); + var multiTfm3 = ProjectModifierHelper.ProcessCsprojFile(MultiTfmVariabledCsproj2); + Assert.True(multiTfm.Length == 2 && net7Tfms2.Length == 1 && multiTfm3.Length == 3); + Assert.True(multiTfm.Contains("net6.0") && multiTfm.Contains("net7.0")); + Assert.True(multiTfm2.Contains("net6.0") && multiTfm2.Contains("net7.0")); + Assert.True(multiTfm3.Contains("net5.0") && multiTfm3.Contains("net6.0") && multiTfm3.Contains("net7.0")); + } + private static readonly ModelType startupModel = new ModelType { Name = "Startup",