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",