Skip to content

Commit

Permalink
Revert: Improve MSBuild SDK resolver by parsing global.json with Syst…
Browse files Browse the repository at this point in the history
…em.Runtime.Serialization.Json #3157 (#3203)
  • Loading branch information
jeffkl committed Jan 24, 2020
1 parent fa47ea4 commit 2e063a2
Show file tree
Hide file tree
Showing 5 changed files with 1,473 additions and 158 deletions.
143 changes: 73 additions & 70 deletions src/NuGet.Core/Microsoft.Build.NuGetSdkResolver/GlobalJsonReader.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using Microsoft.Build.Shared;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Xml;
using System.Linq;
using System.Runtime.CompilerServices;
using Microsoft.Build.Framework;

namespace Microsoft.Build.NuGetSdkResolver
Expand All @@ -25,26 +28,27 @@ internal static class GlobalJsonReader
/// <returns>A <see cref="Dictionary{String,String}"/> of MSBuild SDK versions from a global.json if found, otherwise <code>null</code>.</returns>
public static Dictionary<string, string> GetMSBuildSdkVersions(SdkResolverContext context)
{
var projectFile = new FileInfo(context.ProjectFilePath);
var projectDirectory = Directory.GetParent(context.ProjectFilePath);

if (!TryGetPathOfFileAbove(GlobalJsonFileName, projectFile.Directory, out var globalJsonPath))
if (projectDirectory == null
|| !projectDirectory.Exists
|| !TryGetPathOfFileAbove(GlobalJsonFileName, projectDirectory.FullName, out var globalJsonPath))
{
return null;
}

// Read the contents of global.json
var globalJsonContents = File.ReadAllText(globalJsonPath);
var contents = File.ReadAllText(globalJsonPath);

// Look ahead in the contents to see if there is an msbuild-sdks section. Deserializing the file requires us to load
// additional assemblies which can be a waste since global.json is usually ~100 bytes of text.
if (globalJsonContents.IndexOf(MSBuildSdksPropertyName, StringComparison.Ordinal) == -1)
// Newtonsoft.Json which is 500 KB while a global.json is usually ~100 bytes of text.
if (contents.IndexOf(MSBuildSdksPropertyName, StringComparison.Ordinal) == -1)
{
return null;
}

try
{
return ParseMSBuildSdksFromGlobalJson(globalJsonPath);
return Deserialize(contents);
}
catch (Exception e)
{
Expand All @@ -56,86 +60,85 @@ internal static class GlobalJsonReader
}

/// <summary>
/// Searches for a file based on the specified starting directory.
/// Deserializes a global.json and returns the MSBuild SDK versions
/// </summary>
/// <param name="fileName">The name of the file to search for.</param>
/// <param name="startingDirectory">An optional <see cref="DirectoryInfo" /> representing the directory to start the search in.</param>
/// <param name="result">Receives the <see cref="FileInfo" /> of the file if found, otherwise <code>null</code>.</param>
/// <returns><code>true</code> if a file was found in the directory or any parent directory, otherwise <code>false</code>.</returns>
public static bool TryGetPathOfFileAbove(string fileName, DirectoryInfo startingDirectory, out string result)
[MethodImpl(MethodImplOptions.NoInlining)]
private static Dictionary<string, string> Deserialize(string value)
{
result = null;

if (startingDirectory == null || !startingDirectory.Exists)
{
return false;
}

var lookInDirectory = startingDirectory;
return JsonConvert.DeserializeObject<GlobalJsonFile>(value).MSBuildSdks;
}

do
{
var possibleFile = new FileInfo(Path.Combine(lookInDirectory.FullName, fileName));
private sealed class GlobalJsonFile
{
[JsonProperty(MSBuildSdksPropertyName)]
public Dictionary<string, string> MSBuildSdks { get; set; }
}

if (possibleFile.Exists)
{
result = possibleFile.FullName;
/// <summary>
/// Searches for a file based on the specified starting directory.
/// </summary>
/// <param name="file">The file to search for.</param>
/// <param name="startingDirectory">An optional directory to start the search in. The default location is the directory
/// of the file containing the property function.</param>
/// <returns>The full path of the file if it is found, otherwise an empty string.</returns>
private static string GetPathOfFileAbove(string file, string startingDirectory)
{
// Search for a directory that contains that file
var directoryName = GetDirectoryNameOfFileAbove(startingDirectory, file);

return true;
}
return string.IsNullOrEmpty(directoryName) ? string.Empty : NormalizePath(Path.Combine(directoryName, file));
}

lookInDirectory = lookInDirectory.Parent;
}
while (lookInDirectory != null);
private static bool TryGetPathOfFileAbove(string file, string startingDirectory, out string fullPath)
{
fullPath = GetPathOfFileAbove(file, startingDirectory);

return false;
return fullPath != string.Empty;
}

/// <summary>
/// Parses a global.json and returns the MSBuild SDK versions.
/// Locate a file in either the directory specified or a location in the
/// directory structure above that directory.
/// </summary>
/// <remarks>
/// NoInlining is enabled ensure that System.Runtime.Serialization.Json.dll isn't loaded unless the method is called.
/// </remarks>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
private static Dictionary<string, string> ParseMSBuildSdksFromGlobalJson(string path)
private static string GetDirectoryNameOfFileAbove(string startingDirectory, string fileName)
{
Dictionary<string, string> sdks = null;
// Canonicalize our starting location
var lookInDirectory = Path.GetFullPath(startingDirectory);

using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
using (var reader = System.Runtime.Serialization.Json.JsonReaderWriterFactory.CreateJsonReader(stream, XmlDictionaryReaderQuotas.Max))
do
{
while (reader.Read())
// Construct the path that we will use to test against
var possibleFileDirectory = Path.Combine(lookInDirectory, fileName);

// If we successfully locate the file in the directory that we're
// looking in, simply return that location. Otherwise we'll
// keep moving up the tree.
if (File.Exists(possibleFileDirectory))
{
// We've found the file, return the directory we found it in
return lookInDirectory;
}
else
{
if (reader.LocalName.Equals(MSBuildSdksPropertyName) && reader.Depth == 1)
{
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element)
{
string name = reader.LocalName.Trim();

if (!string.IsNullOrWhiteSpace(name) && reader.Read() && reader.NodeType == XmlNodeType.Text)
{
if (sdks == null)
{
sdks = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}

var value = reader.Value.Trim();

if (!string.IsNullOrWhiteSpace(value))
{
sdks[name] = value;
}
}
}
}
}
// GetDirectoryName will return null when we reach the root
// terminating our search
lookInDirectory = Path.GetDirectoryName(lookInDirectory);
}
}
while (lookInDirectory != null);

// When we didn't find the location, then return an empty string
return string.Empty;
}

return sdks;
private static string NormalizePath(string path)
{
return FixFilePath(Path.GetFullPath(path));

}
private static string FixFilePath(string path)
{
return string.IsNullOrEmpty(path) || Path.DirectorySeparatorChar == '\\' ? path : path.Replace('\\', '/');
}
}
}

0 comments on commit 2e063a2

Please sign in to comment.