Skip to content

Commit

Permalink
[MsIdentity] parse csproj file and add tfm(s). (#2016)
Browse files Browse the repository at this point in the history
* parse csproj file and add tfm(s).

* removing empty test.
  • Loading branch information
deepchoudhery authored Sep 13, 2022
1 parent 4bb5a46 commit f1b0ee4
Show file tree
Hide file tree
Showing 7 changed files with 243 additions and 3 deletions.
2 changes: 1 addition & 1 deletion scripts/install-msidentity.cmd
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
set VERSION=1.0.0-dev
set VERSION=2.0.0-dev
set NUPKG=artifacts\packages\Debug\Shipping\

pushd %~dp0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ public class ProvisioningToolOptions : IDeveloperCredentialsOptions
/// </summary>
public string? ProjectFilePath { get; set; }

/// <summary>
/// Short target framework from the given ProjectFilePath. List to allow multiple tfms.
/// eg. net6.0, net7.0 etc.
/// </summary>
public IList<string> ShortTfms { get; set; } = new List<string>();

/// <summary>
/// Path to appsettings.json file
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -84,6 +86,64 @@ internal static async Task<bool> IsUsingTopLevelStatements(IModelTypesLocator mo
return true;
}

/// <summary>
/// Parses the csproj xml text and gets one or more TargetFrameworks for the project.
/// </summary>
/// <param name="csprojText">.csproj file as string</param>
/// <returns>string[] containing target frameworks of the project</returns>
internal static string[] ProcessCsprojFile(string csprojText)
{
List<string> processedTfms = new List<string>();
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<string, string>(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 <TargetFramework>$(X)</TargetFramework> 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<bool> IsMinimalApp(List<Document> documents)
{
Expand Down Expand Up @@ -522,5 +582,45 @@ internal static CodeBlock[] FilterCodeBlocks(CodeBlock[] codeBlocks, CodeChangeO
}
return filteredCodeBlocks.ToArray();
}

/// <summary>
/// Take the tfm value in the csproj and use the Dictionary of variables to find its true value.
/// </summary>
/// <param name="tfm">value for <TargetFramework/> or '<TargetFrameworks/> in the csproj file</param>
/// <param name="csprojVariables">dictionary with all csproj properties and values</param>
/// <returns></returns>
internal static string ProcessTfm(string tfm, Dictionary<string, string> 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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public static string GetProjectAssetsFile(IProjectContext projectInformation)
return string.Empty;
}

private static Dictionary<string, string> ShortTfmDictionary = new Dictionary<string, string>()
internal static Dictionary<string, string> ShortTfmDictionary = new Dictionary<string, string>()
{
{ ".NETCoreApp,Version=v3.1", "netcoreapp3.1" },
{ ".NETCoreApp,Version=v5.0", "net5.0" },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,111 @@ protected static List<ParameterSyntax> CreateParameterList(string[] types, strin
return paramList;
}

protected const string Net7Csproj = @"<Project Sdk=""Microsoft.NET.Sdk.Web"">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>
";

protected const string Net7CsprojVariabledCsproj = @"<Project Sdk=""Microsoft.NET.Sdk.Web"">
<PropertyGroup>
<Var1>$(Var2)$(Var3)</Var1>
<Var2>net</Var2>
<Var3>7.0</Var3>
<TargetFramework>$(Var1)</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>
";

protected const string Net7CsprojVariabledCsproj2 = @"<Project Sdk=""Microsoft.NET.Sdk.Web"">
<PropertyGroup>
<Var1>net7.0</Var1>
<TargetFramework>$(Var1)</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>
";

protected const string MultiTfmCsproj = @"<Project Sdk=""Microsoft.NET.Sdk.Web"">
<PropertyGroup>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>
";

protected const string EmptyCsproj = @"<Project Sdk=""Microsoft.NET.Sdk.Web"">
</Project>
";
protected const string EmptyCsproj2 = @"";

protected const string MultiTfmVariabledCsproj = @"<Project Sdk=""Microsoft.NET.Sdk.Web"">
<PropertyGroup>
<Var1>$(Var2);$(Var3)</Var1>
<Var2>net7.0</Var2>
<Nullable>enable</Nullable>
<Var3>net6.0</Var3>
<TargetFrameworks>$(Var1)</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>
";
protected const string MultiTfmVariabledCsproj2 = @"<Project Sdk=""Microsoft.NET.Sdk.Web"">
<PropertyGroup>
<Var1>$(Var2)$(Var3)</Var1>
<Var2>net</Var2>
<Var3>7.0</Var3>
<Nullable>enable</Nullable>
<Var4>net6.0</Var4>
<TargetFrameworks>$(Var1);$(Var4);net5.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>
";
protected const string InvalidCsproj = @"<Project Sdk=""Microsoft.NET.Sdk.Web"">
<PropertyGroup>
<TargetFramework>net69.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>
";
protected const string InvalidCsproj2 = @"<Project Sdk=""Microsoft.NET.Sdk.Web"">
<PropertyGroup>
<Var1>$(Var2);$(Var3)</Var1>
<Var2>net77.0</Var2>
<Nullable>enable</Nullable>
<Var3>net69.0</Var3>
<TargetFrameworks>$(Var1)</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>
";
protected const string InvalidCsproj3 = @"<Project Sdk=""Microsoft.NET.Sdk.Web"">
<PropertyGroup>
<Var1>net77.0</Var1>
<TargetFramework>$(Var1)</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>
";

protected const string FullDocument = @"
using System;
using System.Duplicate;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit f1b0ee4

Please sign in to comment.