Skip to content

Commit

Permalink
Introduction of strongly typed immutable representation of the dotnet…
Browse files Browse the repository at this point in the history
… --info output.
  • Loading branch information
Corniel committed Feb 19, 2024
1 parent 35f12d2 commit 559295c
Show file tree
Hide file tree
Showing 6 changed files with 255 additions and 2 deletions.
2 changes: 0 additions & 2 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<CodeAnalysisTreatWarningsAsErrors>true</CodeAnalysisTreatWarningsAsErrors>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
</PropertyGroup>
Expand Down
1 change: 1 addition & 0 deletions src/Buildalyzer/Buildalyzer.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>12</LangVersion>
<Description>A little utility to perform design-time builds of .NET projects without having to think too hard about it. Should work with any project type on any .NET runtime.</Description>
</PropertyGroup>
<ItemGroup>
Expand Down
166 changes: 166 additions & 0 deletions src/Buildalyzer/Environment/DotNetInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
#nullable enable

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace Buildalyzer.Environment;

public sealed class DotNetInfo
{
public Version? SdkVersion { get; init; }
public string? OSName { get; init; }
public string? OSPlatform { get; init; }
public Version? OSVersion { get; init; }

public DirectoryInfo? BasePath { get; init; }

public FileInfo? GlobalJson { get; init; }

public IReadOnlyDictionary<string, DirectoryInfo> SDKs { get; init; } = new Dictionary<string, DirectoryInfo>();

public IReadOnlyDictionary<string, DirectoryInfo> Runtimes { get; init; } = new Dictionary<string, DirectoryInfo>();

public static DotNetInfo Parse(string? s)
=> Parse(s?.Split([System.Environment.NewLine], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) ?? []);

Check warning on line 26 in src/Buildalyzer/Environment/DotNetInfo.cs

View workflow job for this annotation

GitHub Actions / Build (windows-latest)

Check warning on line 26 in src/Buildalyzer/Environment/DotNetInfo.cs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest)

Check warning on line 26 in src/Buildalyzer/Environment/DotNetInfo.cs

View workflow job for this annotation

GitHub Actions / Build (macos-latest)


public static DotNetInfo Parse(IEnumerable<string> lines)
{
var header = string.Empty;

Check warning on line 30 in src/Buildalyzer/Environment/DotNetInfo.cs

View workflow job for this annotation

GitHub Actions / Build (windows-latest)

Use explicit type instead of 'var'. (http://pihrt.net/roslynator/analyzer?id=RCS1008)

Check warning on line 30 in src/Buildalyzer/Environment/DotNetInfo.cs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest)

Use explicit type instead of 'var'. (http://pihrt.net/roslynator/analyzer?id=RCS1008)

Check warning on line 30 in src/Buildalyzer/Environment/DotNetInfo.cs

View workflow job for this annotation

GitHub Actions / Build (macos-latest)

Use explicit type instead of 'var'. (http://pihrt.net/roslynator/analyzer?id=RCS1008)

Version? sdkVersion = null;
string? osName = null;
string? osPlatform = null;
Version? osVersion = null;
DirectoryInfo? basePath = null;
FileInfo? globalJson = null;
var sdks = new Dictionary<string, DirectoryInfo>();

Check warning on line 38 in src/Buildalyzer/Environment/DotNetInfo.cs

View workflow job for this annotation

GitHub Actions / Build (windows-latest)

Use explicit type instead of 'var'. (http://pihrt.net/roslynator/analyzer?id=RCS1008)

Check warning on line 38 in src/Buildalyzer/Environment/DotNetInfo.cs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest)

Use explicit type instead of 'var'. (http://pihrt.net/roslynator/analyzer?id=RCS1008)

Check warning on line 38 in src/Buildalyzer/Environment/DotNetInfo.cs

View workflow job for this annotation

GitHub Actions / Build (macos-latest)

Use explicit type instead of 'var'. (http://pihrt.net/roslynator/analyzer?id=RCS1008)
var runtimes = new Dictionary<string, DirectoryInfo>();

Check warning on line 40 in src/Buildalyzer/Environment/DotNetInfo.cs

View workflow job for this annotation

GitHub Actions / Build (windows-latest)

Check warning on line 40 in src/Buildalyzer/Environment/DotNetInfo.cs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest)

Check warning on line 40 in src/Buildalyzer/Environment/DotNetInfo.cs

View workflow job for this annotation

GitHub Actions / Build (macos-latest)

foreach (var line in lines?.Select(l => l.Trim()).Where(l => l is { Length: > 0 }) ?? [])

Check warning on line 41 in src/Buildalyzer/Environment/DotNetInfo.cs

View workflow job for this annotation

GitHub Actions / Build (windows-latest)

Check warning on line 41 in src/Buildalyzer/Environment/DotNetInfo.cs

View workflow job for this annotation

GitHub Actions / Build (windows-latest)

Use explicit type instead of 'var'. (http://pihrt.net/roslynator/analyzer?id=RCS1009)

Check warning on line 41 in src/Buildalyzer/Environment/DotNetInfo.cs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest)

Check warning on line 41 in src/Buildalyzer/Environment/DotNetInfo.cs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest)

Use explicit type instead of 'var'. (http://pihrt.net/roslynator/analyzer?id=RCS1009)

Check warning on line 41 in src/Buildalyzer/Environment/DotNetInfo.cs

View workflow job for this annotation

GitHub Actions / Build (macos-latest)

Check warning on line 41 in src/Buildalyzer/Environment/DotNetInfo.cs

View workflow job for this annotation

GitHub Actions / Build (macos-latest)

Use explicit type instead of 'var'. (http://pihrt.net/roslynator/analyzer?id=RCS1009)
{
// Update read header.
if (Headers.Contains(line))
{
header = line.ToUpperInvariant();
continue;
}

switch (header)
{
case ".NET SDK:":
sdkVersion ??= Version("Version:", line);
break;

case "RUNTIME ENVIRONMENT:":
basePath ??= BasePath(line);
osName ??= Label("OS Name:", line);
osPlatform ??= Label("OS Platform:", line);
osVersion ??= Version("OS Version:", line);
break;

case ".NET SDKS INSTALLED:":
case ".NET CORE SDKS INSTALLED:":
AddSdk(line);
break;

case ".NET RUNTIMES INSTALLED:":
AddRunTime(line);
break;

case "GLOBAL.JSON FILE:":
globalJson ??= GlobalJson(line);
break;
}
}

return new DotNetInfo()
{
SdkVersion = sdkVersion,
OSName = osName,
OSPlatform = osPlatform,
OSVersion = osVersion,
BasePath = basePath,
GlobalJson = globalJson,
SDKs = sdks,
Runtimes = runtimes,
};

static Version? Version(string prefix, string line)
=> line.StartsWith(prefix, IgnoreCase) && System.Version.TryParse(line[prefix.Length..].Trim(), out var parsed)
? parsed
: null;

static string? Label(string prefix,string line)

Check warning on line 95 in src/Buildalyzer/Environment/DotNetInfo.cs

View workflow job for this annotation

GitHub Actions / Build (windows-latest)

Check warning on line 95 in src/Buildalyzer/Environment/DotNetInfo.cs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest)

Check warning on line 95 in src/Buildalyzer/Environment/DotNetInfo.cs

View workflow job for this annotation

GitHub Actions / Build (macos-latest)

=> line.StartsWith(prefix, IgnoreCase) && line[prefix.Length..].Trim() is { Length: > 0 } label
? label
: null;

static DirectoryInfo? BasePath(string line)
{
if (line.StartsWith("Base Path:", IgnoreCase))
{
var path = line[10..].Trim();

// Make sure the base path matches the runtime architecture if on Windows
// Note that this only works for the default installation locations under "Program Files"
if (path.Contains(@"\Program Files\") && !System.Environment.Is64BitProcess)
{
string newBasePath = path.Replace(@"\Program Files\", @"\Program Files (x86)\");
if (Directory.Exists(newBasePath))
{
path = newBasePath;
}
}
else if (path.Contains(@"\Program Files (x86)\") && System.Environment.Is64BitProcess)
{
string newBasePath = path.Replace(@"\Program Files (x86)\", @"\Program Files\");
if (Directory.Exists(newBasePath))
{
path = newBasePath;
}
}

return new DirectoryInfo(path);
}
else
{
return null;
}
}

static FileInfo? GlobalJson(string line)
=> line.Equals("Not found", IgnoreCase)
? null
: new FileInfo(line);

void AddSdk(string line)
{
if (line.Split(new[] { '[', ']' }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) is { Length: 2 } parts)
{
sdks[parts[0]] = new(Path.Combine(parts[1], parts[0]));

Check warning on line 142 in src/Buildalyzer/Environment/DotNetInfo.cs

View workflow job for this annotation

GitHub Actions / Build (windows-latest)

Check warning on line 142 in src/Buildalyzer/Environment/DotNetInfo.cs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest)

Check warning on line 142 in src/Buildalyzer/Environment/DotNetInfo.cs

View workflow job for this annotation

GitHub Actions / Build (macos-latest)

}
}
void AddRunTime(string line)
{
if (line.Split(new[] { '[', ']' }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) is { Length: 2 } parts)
{
runtimes[parts[0]] = new(parts[1]);

Check warning on line 149 in src/Buildalyzer/Environment/DotNetInfo.cs

View workflow job for this annotation

GitHub Actions / Build (windows-latest)

Check warning on line 149 in src/Buildalyzer/Environment/DotNetInfo.cs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest)

Check warning on line 149 in src/Buildalyzer/Environment/DotNetInfo.cs

View workflow job for this annotation

GitHub Actions / Build (macos-latest)

}
}
}

private const StringComparison IgnoreCase = StringComparison.OrdinalIgnoreCase;

private static readonly HashSet<string> Headers = new(StringComparer.InvariantCultureIgnoreCase)

Check warning on line 156 in src/Buildalyzer/Environment/DotNetInfo.cs

View workflow job for this annotation

GitHub Actions / Build (windows-latest)

Check warning on line 156 in src/Buildalyzer/Environment/DotNetInfo.cs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest)

Check warning on line 156 in src/Buildalyzer/Environment/DotNetInfo.cs

View workflow job for this annotation

GitHub Actions / Build (macos-latest)

{
".NET SDK:",
"Runtime Environment:",
".NET SDKs installed:",
".NET Core SDKs installed:",
".NET runtimes installed:",
"Other architectures found:",
"global.json file:",
};
}
3 changes: 3 additions & 0 deletions src/Buildalyzer/Environment/DotnetPathResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ private List<string> GetInfo(string projectPath, string dotnetExePath)
// global.json may change the version, so need to set working directory
using (ProcessRunner processRunner = new ProcessRunner(dotnetExePath, "--info", Path.GetDirectoryName(projectPath), environmentVariables, _loggerFactory))
{
Debugger.Launch();

int dotnetInfoWaitTime = int.TryParse(System.Environment.GetEnvironmentVariable(EnvironmentVariables.DOTNET_INFO_WAIT_TIME), out int dotnetInfoWaitTimeParsed)
? dotnetInfoWaitTimeParsed
: DefaultDotNetInfoWaitTime;
Expand All @@ -74,6 +76,7 @@ private List<string> GetInfo(string projectPath, string dotnetExePath)
// Try to find a base path
internal static string ParseBasePath(List<string> lines)
{
Debugger.Break();
foreach (string line in lines.Where(x => x != null))
{
int colonIndex = line.IndexOf(':');
Expand Down
1 change: 1 addition & 0 deletions tests/Buildalyzer.Tests/Buildalyzer.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="LibGit2Sharp" Version="0.27.0-preview-0175" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
Expand Down
84 changes: 84 additions & 0 deletions tests/Buildalyzer.Tests/Environment/DotNetInfoFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System;
using Buildalyzer.Environment;
using FluentAssertions;
using NUnit.Framework;

namespace Buildalyzer.Tests.Environment;

public class DotNetInfoFixture
{
[Test]
public void Parses()
{
var lines = @"
.NET SDK:
Version: 8.0.200
Commit: 438cab6a9d
Workload version: 8.0.200-manifests.e575128c
Runtime Environment:
OS Name: Windows
OS Version: 10.0.22631
OS Platform: Windows10
RID: win-x64
Base Path: C:\Program Files\dotnet\sdk\8.0.200\
.NET workloads installed:
There are no installed workloads to display.
Host:
Version: 8.0.2
Architecture: x64
Commit: 1381d5ebd2
.NET SDKs installed:
8.0.200 [C:\Program Files\dotnet\sdk]
.NET runtimes installed:
Microsoft.AspNetCore.All 2.1.30 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.App 2.1.30 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.32 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 6.0.27 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 7.0.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 7.0.16 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 8.0.2 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 2.1.30 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.32 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 6.0.9 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 6.0.20 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 6.0.27 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 7.0.0-rc.2.22472.3 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 7.0.7 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 7.0.16 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 8.0.2 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 3.1.32 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 6.0.20 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 6.0.27 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 7.0.7 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 7.0.16 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 8.0.2 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Other architectures found:
x86 [C:\Program Files (x86)\dotnet]
registered at [HKLM\SOFTWARE\dotn
et\Setup\InstalledVersions\x86\InstallLocation]
Environment variables:
Not set
global.json file:
Not found
Learn more:
https://aka.ms/dotnet/info
Download .NET:
https://aka.ms/dotnet/download";

var info = DotNetInfo.Parse(lines);

info.Should().BeEquivalentTo(new
{
SdkVersion = new Version(8, 0, 200),
OSName = "Windows",
OSVersion = new Version(10, 0, 22631),
OSPlatform = "Windows10",
SDKs = new { Count = 1, },
Runtimes = new { Count = 25, },
});
info.BasePath.ToString().Should().Be(@"C:\Program Files\dotnet\sdk\8.0.200\");
}
}

0 comments on commit 559295c

Please sign in to comment.