Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MsIdentity] parse csproj file and add tfm(s). #2016

Merged
merged 2 commits into from Sep 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion 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
Expand Down
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
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
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 @@ -77,6 +79,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 @@ -517,5 +577,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;
}
}
}
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
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
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