Skip to content

Commit f1b0ee4

Browse files
[MsIdentity] parse csproj file and add tfm(s). (#2016)
* parse csproj file and add tfm(s). * removing empty test.
1 parent 4bb5a46 commit f1b0ee4

File tree

7 files changed

+243
-3
lines changed

7 files changed

+243
-3
lines changed

scripts/install-msidentity.cmd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
set VERSION=1.0.0-dev
1+
set VERSION=2.0.0-dev
22
set NUPKG=artifacts\packages\Debug\Shipping\
33

44
pushd %~dp0

src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/CodeReaderWriter/ProjectModifier.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,11 @@ public async Task AddAuthCodeAsync()
5353
_consoleLogger.LogJsonMessage(new JsonResponse(Commands.UPDATE_PROJECT_COMMAND, State.Fail, output: errorMsg));
5454
return;
5555
}
56-
56+
5757
_toolOptions.ProjectFilePath = csProjFiles.First();
5858
}
59+
string csprojText = File.ReadAllText(_toolOptions.ProjectFilePath);
60+
_toolOptions.ShortTfms = ProjectModifierHelper.ProcessCsprojFile(csprojText);
5961

6062
CodeModifierConfig? codeModifierConfig = GetCodeModifierConfig();
6163
if (codeModifierConfig is null || !codeModifierConfig.Files.Any())

src/MSIdentityScaffolding/Microsoft.DotNet.MSIdentity/Tool/ProvisioningToolOptions.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ public class ProvisioningToolOptions : IDeveloperCredentialsOptions
1414
/// </summary>
1515
public string? ProjectFilePath { get; set; }
1616

17+
/// <summary>
18+
/// Short target framework from the given ProjectFilePath. List to allow multiple tfms.
19+
/// eg. net6.0, net7.0 etc.
20+
/// </summary>
21+
public IList<string> ShortTfms { get; set; } = new List<string>();
22+
1723
/// <summary>
1824
/// Path to appsettings.json file
1925
/// </summary>

src/Shared/Microsoft.DotNet.Scaffolding.Shared/Project/ProjectModifierHelper.cs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
using System.Text;
66
using System.Text.RegularExpressions;
77
using System.Threading.Tasks;
8+
using System.Xml.Linq;
89
using Microsoft.CodeAnalysis;
910
using Microsoft.CodeAnalysis.CSharp.Syntax;
1011
using Microsoft.CodeAnalysis.Text;
1112
using Microsoft.DotNet.Scaffolding.Shared.CodeModifier;
1213
using Microsoft.DotNet.Scaffolding.Shared.CodeModifier.CodeChange;
14+
using Microsoft.DotNet.Scaffolding.Shared.ProjectModel;
1315

1416
namespace Microsoft.DotNet.Scaffolding.Shared.Project
1517
{
@@ -84,6 +86,64 @@ internal static async Task<bool> IsUsingTopLevelStatements(IModelTypesLocator mo
8486
return true;
8587
}
8688

89+
/// <summary>
90+
/// Parses the csproj xml text and gets one or more TargetFrameworks for the project.
91+
/// </summary>
92+
/// <param name="csprojText">.csproj file as string</param>
93+
/// <returns>string[] containing target frameworks of the project</returns>
94+
internal static string[] ProcessCsprojFile(string csprojText)
95+
{
96+
List<string> processedTfms = new List<string>();
97+
if (!string.IsNullOrEmpty(csprojText))
98+
{
99+
//use XDocument to get all csproj elements.
100+
XDocument document = XDocument.Parse(csprojText);
101+
var docNodes = document.Root?.Elements();
102+
var allElements = docNodes?.SelectMany(x => x.Elements());
103+
//add them to a dictionary for easy comparisons.
104+
var csprojVariables = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
105+
if (allElements != null && allElements.Any())
106+
{
107+
foreach (var elem in allElements)
108+
{
109+
//dont' add PackageReference(s) since they are useless for getting tfm properties.
110+
if (!elem.Name.LocalName.Equals("PackageReference", StringComparison.OrdinalIgnoreCase))
111+
{
112+
//change the keys from TargetFramework to $(TargetFramework) and so forth for nested variable analysis.
113+
//eg. analysing <TargetFramework>$(X)</TargetFramework> and getting the value for $(X).
114+
//makes for a easy string comparison without using regex and splitting.
115+
csprojVariables.TryAdd(string.Format("$({0})", elem.Name.LocalName), elem.Value);
116+
}
117+
}
118+
}
119+
120+
//if only one TargetFramework
121+
if (csprojVariables.TryGetValue("$(TargetFramework)", out string tfmValue))
122+
{
123+
string processedTfm = ProcessTfm(tfmValue.Trim(), csprojVariables);
124+
if (!string.IsNullOrEmpty(processedTfm) && ProjectModelHelper.ShortTfmDictionary.Values.ToList().Contains(processedTfm, StringComparer.OrdinalIgnoreCase))
125+
{
126+
processedTfms.Add(processedTfm);
127+
}
128+
}
129+
//if multiple, split by ';' and add them all.
130+
else if (csprojVariables.TryGetValue("$(TargetFrameworks)", out string tfms))
131+
{
132+
string processedTfm = ProcessTfm(tfms.Trim(), csprojVariables);
133+
//tfms should be separated by ;
134+
var splitTfms = processedTfm.Split(";");
135+
foreach (var tfm in splitTfms)
136+
{
137+
if (!string.IsNullOrEmpty(tfm) && ProjectModelHelper.ShortTfmDictionary.Values.ToList().Contains(tfm, StringComparer.OrdinalIgnoreCase))
138+
{
139+
processedTfms.Add(tfm);
140+
}
141+
}
142+
}
143+
}
144+
return processedTfms.ToArray();
145+
}
146+
87147
// Returns true when there is no Startup.cs or equivalent
88148
internal static async Task<bool> IsMinimalApp(List<Document> documents)
89149
{
@@ -522,5 +582,45 @@ internal static CodeBlock[] FilterCodeBlocks(CodeBlock[] codeBlocks, CodeChangeO
522582
}
523583
return filteredCodeBlocks.ToArray();
524584
}
585+
586+
/// <summary>
587+
/// Take the tfm value in the csproj and use the Dictionary of variables to find its true value.
588+
/// </summary>
589+
/// <param name="tfm">value for <TargetFramework/> or '<TargetFrameworks/> in the csproj file</param>
590+
/// <param name="csprojVariables">dictionary with all csproj properties and values</param>
591+
/// <returns></returns>
592+
internal static string ProcessTfm(string tfm, Dictionary<string, string> csprojVariables)
593+
{
594+
if (string.IsNullOrEmpty(tfm))
595+
{
596+
return string.Empty;
597+
}
598+
599+
bool tfmHasVars = true;
600+
while (tfmHasVars)
601+
{
602+
//if the value is in the tfm dictionary (valid values), return it.
603+
if (ProjectModelHelper.ShortTfmDictionary.Values.ToList().Contains(tfm, StringComparer.OrdinalIgnoreCase))
604+
{
605+
return tfm;
606+
}
607+
//if the value has a variable (key) in it, replace it with its value.
608+
else if (tfm.Contains('$'))
609+
{
610+
foreach (var key in csprojVariables.Keys)
611+
{
612+
if (tfm.Contains(key, StringComparison.OrdinalIgnoreCase) && csprojVariables.TryGetValue(key, out string val))
613+
{
614+
tfm = tfm.Replace(key, val);
615+
}
616+
}
617+
}
618+
else
619+
{
620+
return tfm;
621+
}
622+
}
623+
return tfm;
624+
}
525625
}
526626
}

src/Shared/Microsoft.DotNet.Scaffolding.Shared/ProjectModel/ProjectModelHelper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public static string GetProjectAssetsFile(IProjectContext projectInformation)
2727
return string.Empty;
2828
}
2929

30-
private static Dictionary<string, string> ShortTfmDictionary = new Dictionary<string, string>()
30+
internal static Dictionary<string, string> ShortTfmDictionary = new Dictionary<string, string>()
3131
{
3232
{ ".NETCoreApp,Version=v3.1", "netcoreapp3.1" },
3333
{ ".NETCoreApp,Version=v5.0", "net5.0" },

test/Shared/Microsoft.DotNet.Scaffolding.Shared.Tests/DocumentBuilderTestBase.cs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,111 @@ protected static List<ParameterSyntax> CreateParameterList(string[] types, strin
118118
return paramList;
119119
}
120120

121+
protected const string Net7Csproj = @"<Project Sdk=""Microsoft.NET.Sdk.Web"">
122+
<PropertyGroup>
123+
<TargetFramework>net7.0</TargetFramework>
124+
<Nullable>enable</Nullable>
125+
<ImplicitUsings>enable</ImplicitUsings>
126+
</PropertyGroup>
127+
128+
</Project>
129+
";
130+
131+
protected const string Net7CsprojVariabledCsproj = @"<Project Sdk=""Microsoft.NET.Sdk.Web"">
132+
<PropertyGroup>
133+
<Var1>$(Var2)$(Var3)</Var1>
134+
<Var2>net</Var2>
135+
<Var3>7.0</Var3>
136+
<TargetFramework>$(Var1)</TargetFramework>
137+
<Nullable>enable</Nullable>
138+
<ImplicitUsings>enable</ImplicitUsings>
139+
</PropertyGroup>
140+
141+
</Project>
142+
";
143+
144+
protected const string Net7CsprojVariabledCsproj2 = @"<Project Sdk=""Microsoft.NET.Sdk.Web"">
145+
<PropertyGroup>
146+
<Var1>net7.0</Var1>
147+
<TargetFramework>$(Var1)</TargetFramework>
148+
<Nullable>enable</Nullable>
149+
<ImplicitUsings>enable</ImplicitUsings>
150+
</PropertyGroup>
151+
</Project>
152+
";
153+
154+
protected const string MultiTfmCsproj = @"<Project Sdk=""Microsoft.NET.Sdk.Web"">
155+
156+
<PropertyGroup>
157+
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
158+
<Nullable>enable</Nullable>
159+
<ImplicitUsings>enable</ImplicitUsings>
160+
</PropertyGroup>
161+
162+
</Project>
163+
";
164+
165+
protected const string EmptyCsproj = @"<Project Sdk=""Microsoft.NET.Sdk.Web"">
166+
</Project>
167+
";
168+
protected const string EmptyCsproj2 = @"";
169+
170+
protected const string MultiTfmVariabledCsproj = @"<Project Sdk=""Microsoft.NET.Sdk.Web"">
171+
<PropertyGroup>
172+
<Var1>$(Var2);$(Var3)</Var1>
173+
<Var2>net7.0</Var2>
174+
<Nullable>enable</Nullable>
175+
<Var3>net6.0</Var3>
176+
<TargetFrameworks>$(Var1)</TargetFrameworks>
177+
<ImplicitUsings>enable</ImplicitUsings>
178+
</PropertyGroup>
179+
180+
</Project>
181+
";
182+
protected const string MultiTfmVariabledCsproj2 = @"<Project Sdk=""Microsoft.NET.Sdk.Web"">
183+
<PropertyGroup>
184+
<Var1>$(Var2)$(Var3)</Var1>
185+
<Var2>net</Var2>
186+
<Var3>7.0</Var3>
187+
<Nullable>enable</Nullable>
188+
<Var4>net6.0</Var4>
189+
<TargetFrameworks>$(Var1);$(Var4);net5.0</TargetFrameworks>
190+
<ImplicitUsings>enable</ImplicitUsings>
191+
</PropertyGroup>
192+
193+
</Project>
194+
";
195+
protected const string InvalidCsproj = @"<Project Sdk=""Microsoft.NET.Sdk.Web"">
196+
<PropertyGroup>
197+
<TargetFramework>net69.0</TargetFramework>
198+
<Nullable>enable</Nullable>
199+
<ImplicitUsings>enable</ImplicitUsings>
200+
</PropertyGroup>
201+
202+
</Project>
203+
";
204+
protected const string InvalidCsproj2 = @"<Project Sdk=""Microsoft.NET.Sdk.Web"">
205+
<PropertyGroup>
206+
<Var1>$(Var2);$(Var3)</Var1>
207+
<Var2>net77.0</Var2>
208+
<Nullable>enable</Nullable>
209+
<Var3>net69.0</Var3>
210+
<TargetFrameworks>$(Var1)</TargetFrameworks>
211+
<ImplicitUsings>enable</ImplicitUsings>
212+
</PropertyGroup>
213+
214+
</Project>
215+
";
216+
protected const string InvalidCsproj3 = @"<Project Sdk=""Microsoft.NET.Sdk.Web"">
217+
<PropertyGroup>
218+
<Var1>net77.0</Var1>
219+
<TargetFramework>$(Var1)</TargetFramework>
220+
<Nullable>enable</Nullable>
221+
<ImplicitUsings>enable</ImplicitUsings>
222+
</PropertyGroup>
223+
</Project>
224+
";
225+
121226
protected const string FullDocument = @"
122227
using System;
123228
using System.Duplicate;

test/Shared/Microsoft.DotNet.Scaffolding.Shared.Tests/ProjectModifierHelperTests.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,33 @@ public async Task GlobalStatementExistsTests(string[] existingStatements, string
497497
}
498498
}
499499

500+
[Fact]
501+
public void ProcessCsprojFile()
502+
{
503+
var net7Tfms = ProjectModifierHelper.ProcessCsprojFile(Net7Csproj);
504+
var net7Tfms2 = ProjectModifierHelper.ProcessCsprojFile(Net7CsprojVariabledCsproj);
505+
var net7Tfms3 = ProjectModifierHelper.ProcessCsprojFile(Net7CsprojVariabledCsproj2);
506+
Assert.True(net7Tfms.Length == 1 && net7Tfms2.Length == 1 && net7Tfms3.Length == 1);
507+
Assert.True(net7Tfms.First().Equals("net7.0") && net7Tfms2.First().Equals("net7.0") && net7Tfms3.First().Equals("net7.0"));
508+
509+
var emptyTfms = ProjectModifierHelper.ProcessCsprojFile(EmptyCsproj);
510+
var emptyTfms2 = ProjectModifierHelper.ProcessCsprojFile(EmptyCsproj2);
511+
Assert.True(emptyTfms.Length == 0 && emptyTfms2.Length == 0);
512+
513+
var invalidTfm = ProjectModifierHelper.ProcessCsprojFile(InvalidCsproj);
514+
var invalidTfm2 = ProjectModifierHelper.ProcessCsprojFile(InvalidCsproj2);
515+
var invalidTfm3 = ProjectModifierHelper.ProcessCsprojFile(InvalidCsproj3);
516+
Assert.True(invalidTfm.Length == 0 && invalidTfm2.Length == 0 && invalidTfm3.Length == 0);
517+
518+
var multiTfm = ProjectModifierHelper.ProcessCsprojFile(MultiTfmCsproj);
519+
var multiTfm2 = ProjectModifierHelper.ProcessCsprojFile(MultiTfmVariabledCsproj);
520+
var multiTfm3 = ProjectModifierHelper.ProcessCsprojFile(MultiTfmVariabledCsproj2);
521+
Assert.True(multiTfm.Length == 2 && net7Tfms2.Length == 1 && multiTfm3.Length == 3);
522+
Assert.True(multiTfm.Contains("net6.0") && multiTfm.Contains("net7.0"));
523+
Assert.True(multiTfm2.Contains("net6.0") && multiTfm2.Contains("net7.0"));
524+
Assert.True(multiTfm3.Contains("net5.0") && multiTfm3.Contains("net6.0") && multiTfm3.Contains("net7.0"));
525+
}
526+
500527
private static readonly ModelType startupModel = new ModelType
501528
{
502529
Name = "Startup",

0 commit comments

Comments
 (0)