diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4ecbc958..684c7c99 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [0.69.0] - 2025-01-02
+
+### Changed
+
+* We now use the [Microsoft.VisualStudio.SolutionPersistence](https://github.com/microsoft/vs-solutionpersistence) library to parse solution files. This should be more reliable and faster than the previous implementation.
+
## [0.68.0] - 2024-11-18
### Added
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 9c85d113..c279b0d8 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -14,7 +14,7 @@
direct
-
+
@@ -24,18 +24,16 @@
+
+
-
-
-
-
+
+
+
+
diff --git a/README.md b/README.md
index ae3fd6d9..34758aa8 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,6 @@
Parsing and evaluating of `.fsproj` files. This repository contains several packages:
* `Ionide.ProjInfo` - library for parsing and evaluating `.fsproj` files, using `Microsoft.Build` libraries
-* `Ionide.ProjInfo.Sln` - library for parsing `.sln` files
* `Ionide.ProjInfo.FCS` - library providing utility for mapping project data types used by `Ionide.ProjInfo` into `FSharpProjectOptions` type used by `FSharp.Compiler.Service`
* `Ionide.ProjInfo.ProjectSystem` - library providing high level project system component that can be used by editor tooling. It supports features like tracking changes, event-driven notifications about project loading status, and persistent caching of the data for fast initial load.
* `Ionide.ProjInfo.Tool` - a CLI tool intended to help with debugging the cracking of various projects easily
diff --git a/ionide-proj-info.sln b/ionide-proj-info.sln
index e7755496..f3727b12 100644
--- a/ionide-proj-info.sln
+++ b/ionide-proj-info.sln
@@ -7,8 +7,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{0EF20E50-D07
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Ionide.ProjInfo", "src\Ionide.ProjInfo\Ionide.ProjInfo.fsproj", "{B86D70F6-12F9-42E7-8A04-2C21FC91DF9C}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ionide.ProjInfo.Sln", "src\Ionide.ProjInfo.Sln\Ionide.ProjInfo.Sln.csproj", "{7C01A809-1EA9-43A4-BEDC-5488084A22B1}"
-EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{E53FFF53-7874-40D6-8070-EB4E6F5067B9}"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Ionide.ProjInfo.Tests", "test\Ionide.ProjInfo.Tests\Ionide.ProjInfo.Tests.fsproj", "{84BB0C5F-EE12-41C4-ADC9-05FBF54CB7CC}"
@@ -46,18 +44,6 @@ Global
{B86D70F6-12F9-42E7-8A04-2C21FC91DF9C}.Release|x64.Build.0 = Release|Any CPU
{B86D70F6-12F9-42E7-8A04-2C21FC91DF9C}.Release|x86.ActiveCfg = Release|Any CPU
{B86D70F6-12F9-42E7-8A04-2C21FC91DF9C}.Release|x86.Build.0 = Release|Any CPU
- {7C01A809-1EA9-43A4-BEDC-5488084A22B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {7C01A809-1EA9-43A4-BEDC-5488084A22B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {7C01A809-1EA9-43A4-BEDC-5488084A22B1}.Debug|x64.ActiveCfg = Debug|Any CPU
- {7C01A809-1EA9-43A4-BEDC-5488084A22B1}.Debug|x64.Build.0 = Debug|Any CPU
- {7C01A809-1EA9-43A4-BEDC-5488084A22B1}.Debug|x86.ActiveCfg = Debug|Any CPU
- {7C01A809-1EA9-43A4-BEDC-5488084A22B1}.Debug|x86.Build.0 = Debug|Any CPU
- {7C01A809-1EA9-43A4-BEDC-5488084A22B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {7C01A809-1EA9-43A4-BEDC-5488084A22B1}.Release|Any CPU.Build.0 = Release|Any CPU
- {7C01A809-1EA9-43A4-BEDC-5488084A22B1}.Release|x64.ActiveCfg = Release|Any CPU
- {7C01A809-1EA9-43A4-BEDC-5488084A22B1}.Release|x64.Build.0 = Release|Any CPU
- {7C01A809-1EA9-43A4-BEDC-5488084A22B1}.Release|x86.ActiveCfg = Release|Any CPU
- {7C01A809-1EA9-43A4-BEDC-5488084A22B1}.Release|x86.Build.0 = Release|Any CPU
{84BB0C5F-EE12-41C4-ADC9-05FBF54CB7CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{84BB0C5F-EE12-41C4-ADC9-05FBF54CB7CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{84BB0C5F-EE12-41C4-ADC9-05FBF54CB7CC}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -121,7 +107,6 @@ Global
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{B86D70F6-12F9-42E7-8A04-2C21FC91DF9C} = {0EF20E50-D076-480D-BC88-951E5AC8643E}
- {7C01A809-1EA9-43A4-BEDC-5488084A22B1} = {0EF20E50-D076-480D-BC88-951E5AC8643E}
{84BB0C5F-EE12-41C4-ADC9-05FBF54CB7CC} = {E53FFF53-7874-40D6-8070-EB4E6F5067B9}
{AFEB904E-2ACD-4144-BD4B-BEC5772CCFA7} = {0EF20E50-D076-480D-BC88-951E5AC8643E}
{5156E51B-B489-4E0A-865B-79BADB009BD1} = {0EF20E50-D076-480D-BC88-951E5AC8643E}
diff --git a/src/Ionide.ProjInfo.ProjectSystem/WorkspacePeek.fs b/src/Ionide.ProjInfo.ProjectSystem/WorkspacePeek.fs
index 625f9a0f..278579d9 100644
--- a/src/Ionide.ProjInfo.ProjectSystem/WorkspacePeek.fs
+++ b/src/Ionide.ProjInfo.ProjectSystem/WorkspacePeek.fs
@@ -20,8 +20,13 @@ module WorkspacePeek =
| FsProj
| Sln
| Slnf
+ | Slnx
| Fsx
+ []
+ let inline (|HasExt|_|) (ext: string) (file: FileInfo) =
+ if file.Extension = ext then ValueSome() else ValueNone
+
let private partitionByChoice3 =
let foldBy (a, b, c) t =
match t with
@@ -76,26 +81,11 @@ module WorkspacePeek =
topLevelFiles
|> Seq.choose (fun s ->
match s with
- | x when
- x
- |> hasExt ".sln"
- ->
- Some(UsefulFile.Sln, x)
- | x when
- x
- |> hasExt ".slnf"
- ->
- Some(UsefulFile.Slnf, x)
- | x when
- x
- |> hasExt ".fsx"
- ->
- Some(UsefulFile.Fsx, x)
- | x when
- x
- |> hasExt ".fsproj"
- ->
- Some(UsefulFile.FsProj, x)
+ | HasExt ".sln" -> Some(UsefulFile.Sln, s)
+ | HasExt ".slnf" -> Some(UsefulFile.Slnf, s)
+ | HasExt ".fsx" -> Some(UsefulFile.Fsx, s)
+ | HasExt ".fsproj" -> Some(UsefulFile.FsProj, s)
+ | HasExt ".slnx" -> Some(UsefulFile.Slnx, s)
| _ -> None
)
|> Seq.toArray
@@ -141,20 +131,14 @@ module WorkspacePeek =
let getInfo (t, (f: FileInfo)) =
match t with
| UsefulFile.Sln
- | UsefulFile.Slnf ->
+ | UsefulFile.Slnf
+ | UsefulFile.Slnx ->
match InspectSln.tryParseSln f.FullName with
- | Ok(p, d) -> Some(Choice1Of3(p, d))
+ | Ok(d) -> Some(Choice1Of3(f.FullName, d))
| Error e ->
- let addInfo l =
- match e with
- | :? Ionide.ProjInfo.Sln.Exceptions.InvalidProjectFileException as ipfe -> Log.addContextDestructured "data" ipfe l
-
- | _ -> l
-
logger.warn (
Log.setMessage "Failed to load file: {filePath} : {data}"
>> Log.addContext "filePath" f.FullName
- >> addInfo
>> Log.addExn e
)
diff --git a/src/Ionide.ProjInfo.Sln/ExceptionHandling.cs b/src/Ionide.ProjInfo.Sln/ExceptionHandling.cs
deleted file mode 100644
index e8a8620b..00000000
--- a/src/Ionide.ProjInfo.Sln/ExceptionHandling.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Security;
-using System.IO;
-
-namespace Ionide.ProjInfo.Sln.Shared
-{
- internal static class ExceptionHandling
- {
- ///
- /// Determine whether the exception is file-IO related.
- ///
- /// The exception to check.
- /// True if exception is IO related.
- public static bool IsIoRelatedException(Exception e)
- {
- // These all derive from IOException
- // DirectoryNotFoundException
- // DriveNotFoundException
- // EndOfStreamException
- // FileLoadException
- // FileNotFoundException
- // PathTooLongException
- // PipeException
- return e is UnauthorizedAccessException
- || e is NotSupportedException
- || (e is ArgumentException && !(e is ArgumentNullException))
- || e is SecurityException
- || e is IOException;
- }
- }
-}
diff --git a/src/Ionide.ProjInfo.Sln/FileUtilities.cs b/src/Ionide.ProjInfo.Sln/FileUtilities.cs
deleted file mode 100644
index 3128b27d..00000000
--- a/src/Ionide.ProjInfo.Sln/FileUtilities.cs
+++ /dev/null
@@ -1,1424 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-using System;
-#if !CLR2COMPATIBILITY
-using System.Collections.Concurrent;
-#else
-using Microsoft.Build.Shared.Concurrent;
-#endif
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
-using System.IO;
-using System.Globalization;
-using System.Linq;
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Text;
-using System.Threading;
-using System.Runtime.InteropServices;
-using System.Text.RegularExpressions;
-namespace Ionide.ProjInfo.Sln.Shared
-{
-
- internal static class FileUtilitiesRegex
- {
- // regular expression used to match file-specs beginning with ":"
- internal static readonly Regex DrivePattern = new Regex(@"^[A-Za-z]:");
-
- // regular expression used to match UNC paths beginning with "\\\"
- internal static readonly Regex UNCPattern = new Regex(String.Format(CultureInfo.InvariantCulture,
- @"^[\{0}\{1}][\{0}\{1}][^\{0}\{1}]+[\{0}\{1}][^\{0}\{1}]+", Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar));
- }
- ///
- /// This class contains utility methods for file IO.
- /// PERF\COVERAGE NOTE: Try to keep classes in 'shared' as granular as possible. All the methods in
- /// each class get pulled into the resulting assembly.
- ///
- internal static partial class FileUtilities
- {
- // A list of possible test runners. If the program running has one of these substrings in the name, we assume
- // this is a test harness.
-
- // This flag, when set, indicates that we are running tests. Initially assume it's true. It also implies that
- // the currentExecutableOverride is set to a path (that is non-null). Assume this is not initialized when we
- // have the impossible combination of runningTests = false and currentExecutableOverride = null.
-
- // This is the fake current executable we use in case we are running tests.
-
- ///
- /// The directory where MSBuild stores cache information used during the build.
- ///
- internal static string cacheDirectory = null;
-
- ///
- /// FOR UNIT TESTS ONLY
- /// Clear out the static variable used for the cache directory so that tests that
- /// modify it can validate their modifications.
- ///
- internal static void ClearCacheDirectoryPath()
- {
- cacheDirectory = null;
- }
-
- internal static readonly StringComparison PathComparison = GetIsFileSystemCaseSensitive() ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
-
- ///
- /// Determines whether the file system is case sensitive.
- /// Copied from https://github.com/dotnet/runtime/blob/73ba11f3015216b39cb866d9fb7d3d25e93489f2/src/libraries/Common/src/System/IO/PathInternal.CaseSensitivity.cs#L41-L59
- ///
- public static bool GetIsFileSystemCaseSensitive()
- {
- try
- {
- string pathWithUpperCase = Path.Combine(Path.GetTempPath(), "CASESENSITIVETEST" + Guid.NewGuid().ToString("N"));
- using (new FileStream(pathWithUpperCase, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None, 0x1000, FileOptions.DeleteOnClose))
- {
- string lowerCased = pathWithUpperCase.ToLowerInvariant();
- return !File.Exists(lowerCased);
- }
- }
- catch (Exception exc)
- {
- // In case something goes terribly wrong, we don't want to fail just because
- // of a casing test, so we assume case-insensitive-but-preserving.
- Debug.Fail("Casing test failed: " + exc);
- return false;
- }
- }
-
- ///
- /// Copied from https://github.com/dotnet/corefx/blob/056715ff70e14712419d82d51c8c50c54b9ea795/src/Common/src/System/IO/PathInternal.Windows.cs#L61
- /// MSBuild should support the union of invalid path chars across the supported OSes, so builds can have the same behaviour crossplatform: https://github.com/dotnet/msbuild/issues/781#issuecomment-243942514
- ///
- internal static readonly char[] InvalidPathChars = new char[]
- {
- '|', '\0',
- (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10,
- (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20,
- (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30,
- (char)31
- };
-
- ///
- /// Copied from https://github.com/dotnet/corefx/blob/387cf98c410bdca8fd195b28cbe53af578698f94/src/System.Runtime.Extensions/src/System/IO/Path.Windows.cs#L18
- /// MSBuild should support the union of invalid path chars across the supported OSes, so builds can have the same behaviour crossplatform: https://github.com/dotnet/msbuild/issues/781#issuecomment-243942514
- ///
- internal static readonly char[] InvalidFileNameChars = new char[]
- {
- '\"', '<', '>', '|', '\0',
- (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10,
- (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20,
- (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30,
- (char)31, ':', '*', '?', '\\', '/'
- };
-
- internal static readonly char[] Slashes = { '/', '\\' };
-
- internal static readonly string DirectorySeparatorString = Path.DirectorySeparatorChar.ToString();
-
- private static readonly ConcurrentDictionary FileExistenceCache = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase);
-
-
- ///
- /// Retrieves the MSBuild runtime cache directory
- ///
- internal static string GetCacheDirectory()
- {
- if (cacheDirectory == null)
- {
- cacheDirectory = Path.Combine(Path.GetTempPath(), String.Format(CultureInfo.CurrentUICulture, "MSBuild{0}-{1}", Process.GetCurrentProcess().Id, AppDomain.CurrentDomain.Id));
- }
-
- return cacheDirectory;
- }
-
- ///
- /// Get the hex hash string for the string
- ///
- internal static string GetHexHash(string stringToHash)
- {
- return stringToHash.GetHashCode().ToString("X", CultureInfo.InvariantCulture);
- }
-
- ///
- /// Get the hash for the assemblyPaths
- ///
- internal static int GetPathsHash(IEnumerable assemblyPaths)
- {
- StringBuilder builder = new StringBuilder();
-
- foreach (string path in assemblyPaths)
- {
- if (path != null)
- {
- string directoryPath = path.Trim();
- if (directoryPath.Length > 0)
- {
- DateTime lastModifiedTime = System.IO.File.GetLastWriteTimeUtc(directoryPath);
- builder.Append(lastModifiedTime.Ticks);
- builder.Append('|');
- builder.Append(directoryPath.ToUpperInvariant());
- builder.Append('|');
- }
- }
- }
-
- return builder.ToString().GetHashCode();
- }
-
- ///
- /// Clears the MSBuild runtime cache
- ///
- internal static void ClearCacheDirectory()
- {
- string cacheDirectory = GetCacheDirectory();
-
- if (Directory.Exists(cacheDirectory))
- {
- DeleteDirectoryNoThrow(cacheDirectory, true);
- }
- }
-
- ///
- /// If the given path doesn't have a trailing slash then add one.
- /// If the path is an empty string, does not modify it.
- ///
- /// The path to check.
- /// A path with a slash.
- internal static string EnsureTrailingSlash(string fileSpec)
- {
- fileSpec = FixFilePath(fileSpec);
- if (fileSpec.Length > 0 && !IsSlash(fileSpec[fileSpec.Length - 1]))
- {
- fileSpec += Path.DirectorySeparatorChar;
- }
-
- return fileSpec;
- }
-
- ///
- /// Ensures the path does not have a leading or trailing slash after removing the first 'start' characters.
- ///
- internal static string EnsureNoLeadingOrTrailingSlash(string path, int start)
- {
- int stop = path.Length;
- while (start < stop && IsSlash(path[start]))
- {
- start++;
- }
- while (start < stop && IsSlash(path[stop - 1]))
- {
- stop--;
- }
-
- return FixFilePath(path.Substring(start, stop - start));
- }
-
- ///
- /// Ensures the path does not have a leading slash after removing the first 'start' characters but does end in a slash.
- ///
- internal static string EnsureTrailingNoLeadingSlash(string path, int start)
- {
- int stop = path.Length;
- while (start < stop && IsSlash(path[start]))
- {
- start++;
- }
-
- return FixFilePath(start < stop && IsSlash(path[stop - 1]) ?
- path.Substring(start) :
- path.Substring(start) + Path.DirectorySeparatorChar);
- }
-
- ///
- /// Ensures the path does not have a trailing slash.
- ///
- internal static string EnsureNoTrailingSlash(string path)
- {
- path = FixFilePath(path);
- if (EndsWithSlash(path))
- {
- path = path.Substring(0, path.Length - 1);
- }
-
- return path;
- }
-
- ///
- /// Indicates if the given file-spec ends with a slash.
- ///
- /// The file spec.
- /// true, if file-spec has trailing slash
- internal static bool EndsWithSlash(string fileSpec)
- {
- return (fileSpec.Length > 0)
- ? IsSlash(fileSpec[fileSpec.Length - 1])
- : false;
- }
-
- ///
- /// Indicates if the given character is a slash.
- ///
- ///
- /// true, if slash
- internal static bool IsSlash(char c)
- {
- return (c == Path.DirectorySeparatorChar) || (c == Path.AltDirectorySeparatorChar);
- }
-
- ///
- /// Trims the string and removes any double quotes around it.
- ///
- internal static string TrimAndStripAnyQuotes(string path)
- {
- // Trim returns the same string if trimming isn't needed
- path = path.Trim();
- path = path.Trim(new char[] { '"' });
-
- return path;
- }
-
- ///
- /// Get the directory name of a rooted full path
- ///
- ///
- ///
- internal static String GetDirectoryNameOfFullPath(String fullPath)
- {
- if (fullPath != null)
- {
- int i = fullPath.Length;
- while (i > 0 && fullPath[--i] != Path.DirectorySeparatorChar && fullPath[i] != Path.AltDirectorySeparatorChar) ;
- return FixFilePath(fullPath.Substring(0, i));
- }
- return null;
- }
-
- internal static string TruncatePathToTrailingSegments(string path, int trailingSegmentsToKeep)
- {
-#if !CLR2COMPATIBILITY
- ErrorUtilities.VerifyThrowInternalLength(path, nameof(path));
- ErrorUtilities.VerifyThrow(trailingSegmentsToKeep >= 0, "trailing segments must be positive");
-
- var segments = path.Split(Slashes, StringSplitOptions.RemoveEmptyEntries);
-
- var headingSegmentsToRemove = Math.Max(0, segments.Length - trailingSegmentsToKeep);
-
- return string.Join(DirectorySeparatorString, segments.Skip(headingSegmentsToRemove));
-#else
- return path;
-#endif
- }
-
- internal static bool ContainsRelativePathSegments(string path)
- {
- for (int i = 0; i < path.Length; i++)
- {
- if (i + 1 < path.Length && path[i] == '.' && path[i + 1] == '.')
- {
- if (RelativePathBoundsAreValid(path, i, i + 1))
- {
- return true;
- }
- else
- {
- i += 2;
- continue;
- }
- }
-
- if (path[i] == '.' && RelativePathBoundsAreValid(path, i, i))
- {
- return true;
- }
- }
-
- return false;
- }
-
-#if !CLR2COMPATIBILITY
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
-#endif
- private static bool RelativePathBoundsAreValid(string path, int leftIndex, int rightIndex)
- {
- var leftBound = leftIndex - 1 >= 0
- ? path[leftIndex - 1]
- : (char?)null;
-
- var rightBound = rightIndex + 1 < path.Length
- ? path[rightIndex + 1]
- : (char?)null;
-
- return IsValidRelativePathBound(leftBound) && IsValidRelativePathBound(rightBound);
- }
-
-#if !CLR2COMPATIBILITY
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
-#endif
- private static bool IsValidRelativePathBound(char? c)
- {
- return c == null || IsAnySlash(c.Value);
- }
-
- ///
- /// Gets the canonicalized full path of the provided path.
- /// Guidance for use: call this on all paths accepted through public entry
- /// points that need normalization. After that point, only verify the path
- /// is rooted, using ErrorUtilities.VerifyThrowPathRooted.
- /// ASSUMES INPUT IS ALREADY UNESCAPED.
- ///
- internal static string NormalizePath(string path)
- {
- ErrorUtilities.VerifyThrowArgumentLength(path, nameof(path));
- string fullPath = GetFullPath(path);
- return FixFilePath(fullPath);
- }
-
- internal static string NormalizePath(string directory, string file)
- {
- return NormalizePath(Path.Combine(directory, file));
- }
-
-#if !CLR2COMPATIBILITY
- internal static string NormalizePath(params string[] paths)
- {
- return NormalizePath(Path.Combine(paths));
- }
-#endif
-
- private static string GetFullPath(string path)
- {
-#if FEATURE_LEGACY_GETFULLPATH
- if (NativeMethodsShared.IsWindows)
- {
- string uncheckedFullPath = NativeMethodsShared.GetFullPath(path);
-
- if (IsPathTooLong(uncheckedFullPath))
- {
- string message = ResourceUtilities.FormatString(AssemblyResources.GetString("Shared.PathTooLong"), path, NativeMethodsShared.MaxPath);
- throw new PathTooLongException(message);
- }
-
- // We really don't care about extensions here, but Path.HasExtension provides a great way to
- // invoke the CLR's invalid path checks (these are independent of path length)
- Path.HasExtension(uncheckedFullPath);
-
- // If we detect we are a UNC path then we need to use the regular get full path in order to do the correct checks for UNC formatting
- // and security checks for strings like \\?\GlobalRoot
- return IsUNCPath(uncheckedFullPath) ? Path.GetFullPath(uncheckedFullPath) : uncheckedFullPath;
- }
-#endif
- return Path.GetFullPath(path);
- }
-
-#if FEATURE_LEGACY_GETFULLPATH
- private static bool IsUNCPath(string path)
- {
- if (!NativeMethodsShared.IsWindows || !path.StartsWith(@"\\", StringComparison.Ordinal))
- {
- return false;
- }
- bool isUNC = true;
- for (int i = 2; i < path.Length - 1; i++)
- {
- if (path[i] == '\\')
- {
- isUNC = false;
- break;
- }
- }
-
- /*
- From Path.cs in the CLR
-
- Throw an ArgumentException for paths like \\, \\server, \\server\
- This check can only be properly done after normalizing, so
- \\foo\.. will be properly rejected. Also, reject \\?\GLOBALROOT\
- (an internal kernel path) because it provides aliases for drives.
-
- throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegalUNC"));
-
- // Check for \\?\Globalroot, an internal mechanism to the kernel
- // that provides aliases for drives and other undocumented stuff.
- // The kernel team won't even describe the full set of what
- // is available here - we don't want managed apps mucking
- // with this for security reasons.
- */
- return isUNC || path.IndexOf(@"\\?\globalroot", StringComparison.OrdinalIgnoreCase) != -1;
- }
-#endif // FEATURE_LEGACY_GETFULLPATH
-
- internal static string FixFilePath(string path)
- {
- return string.IsNullOrEmpty(path) || Path.DirectorySeparatorChar == '\\' ? path : path.Replace('\\', '/');//.Replace("//", "/");
- }
-
-#if !CLR2COMPATIBILITY
- ///
- /// If on Unix, convert backslashes to slashes for strings that resemble paths.
- /// The heuristic is if something resembles paths (contains slashes) check if the
- /// first segment exists and is a directory.
- /// Use a native shared method to massage file path. If the file is adjusted,
- /// that qualifies is as a path.
- ///
- /// @baseDirectory is just passed to LooksLikeUnixFilePath, to help with the check
- ///
- internal static string MaybeAdjustFilePath(string value, string baseDirectory = "")
- {
- var comparisonType = StringComparison.Ordinal;
-
- // Don't bother with arrays or properties or network paths, or those that
- // have no slashes.
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || string.IsNullOrEmpty(value)
- || value.StartsWith("$(", comparisonType) || value.StartsWith("@(", comparisonType)
- || value.StartsWith("\\\\", comparisonType))
- {
- return value;
- }
-
- // For Unix-like systems, we may want to convert backslashes to slashes
- Span newValue = ConvertToUnixSlashes(value.ToCharArray());
-
- // Find the part of the name we want to check, that is remove quotes, if present
- bool shouldAdjust = newValue.IndexOf('/') != -1 && LooksLikeUnixFilePath(RemoveQuotes(newValue), baseDirectory);
- return shouldAdjust ? newValue.ToString() : value;
- }
-
- ///
- /// If on Unix, convert backslashes to slashes for strings that resemble paths.
- /// This overload takes and returns ReadOnlyMemory of characters.
- ///
- internal static ReadOnlyMemory MaybeAdjustFilePath(ReadOnlyMemory value, string baseDirectory = "")
- {
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || value.IsEmpty)
- {
- return value;
- }
-
- // Don't bother with arrays or properties or network paths.
- if (value.Length >= 2)
- {
- var span = value.Span;
-
- // The condition is equivalent to span.StartsWith("$(") || span.StartsWith("@(") || span.StartsWith("\\\\")
- if ((span[1] == '(' && (span[0] == '$' || span[0] == '@')) ||
- (span[1] == '\\' && span[0] == '\\'))
- {
- return value;
- }
- }
-
- // For Unix-like systems, we may want to convert backslashes to slashes
- Span newValue = ConvertToUnixSlashes(value.ToArray());
-
- // Find the part of the name we want to check, that is remove quotes, if present
- bool shouldAdjust = newValue.IndexOf('/') != -1 && LooksLikeUnixFilePath(RemoveQuotes(newValue), baseDirectory);
- return shouldAdjust ? newValue.ToString().AsMemory() : value;
- }
-
- private static Span ConvertToUnixSlashes(Span path)
- {
- return path.IndexOf('\\') == -1 ? path : CollapseSlashes(path);
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static Span CollapseSlashes(Span str)
- {
- int sliceLength = 0;
-
- // Performs Regex.Replace(str, @"[\\/]+", "/")
- for (int i = 0; i < str.Length; i++)
- {
- bool isCurSlash = IsAnySlash(str[i]);
- bool isPrevSlash = i > 0 && IsAnySlash(str[i - 1]);
-
- if (!isCurSlash || !isPrevSlash)
- {
- str[sliceLength] = str[i] == '\\' ? '/' : str[i];
- sliceLength++;
- }
- }
-
- return str.Slice(0, sliceLength);
- }
-
- private static Span RemoveQuotes(Span path)
- {
- int endId = path.Length - 1;
- char singleQuote = '\'';
- char doubleQuote = '\"';
-
- bool hasQuotes = path.Length > 2
- && ((path[0] == singleQuote && path[endId] == singleQuote)
- || (path[0] == doubleQuote && path[endId] == doubleQuote));
-
- return hasQuotes ? path.Slice(1, endId - 1) : path;
- }
-#endif
-
-#if !CLR2COMPATIBILITY
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
-#endif
- internal static bool IsAnySlash(char c) => c == '/' || c == '\\';
-
-#if !CLR2COMPATIBILITY
- ///
- /// If on Unix, check if the string looks like a file path.
- /// The heuristic is if something resembles paths (contains slashes) check if the
- /// first segment exists and is a directory.
- ///
- /// If @baseDirectory is not null, then look for the first segment exists under
- /// that
- ///
- internal static bool LooksLikeUnixFilePath(string value, string baseDirectory = "")
- => LooksLikeUnixFilePath(value.AsSpan(), baseDirectory);
-
- internal static bool LooksLikeUnixFilePath(ReadOnlySpan value, string baseDirectory = "")
- {
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- {
- return false;
- }
-
- // The first slash will either be at the beginning of the string or after the first directory name
- int directoryLength = value.Slice(1).IndexOf('/') + 1;
- bool shouldCheckDirectory = directoryLength != 0;
-
- // Check for actual files or directories under / that get missed by the above logic
- bool shouldCheckFileOrDirectory = !shouldCheckDirectory && value.Length > 0 && value[0] == '/';
- ReadOnlySpan directory = value.Slice(0, directoryLength);
-
- return (shouldCheckDirectory && Directory.Exists(Path.Combine(baseDirectory, directory.ToString())))
- || (shouldCheckFileOrDirectory && (File.Exists(value.ToString()) || Directory.Exists(value.ToString())));
- }
-#endif
-
- ///
- /// Extracts the directory from the given file-spec.
- ///
- /// The filespec.
- /// directory path
- internal static string GetDirectory(string fileSpec)
- {
- string directory = Path.GetDirectoryName(FixFilePath(fileSpec));
-
- // if file-spec is a root directory e.g. c:, c:\, \, \\server\share
- // NOTE: Path.GetDirectoryName also treats invalid UNC file-specs as root directories e.g. \\, \\server
- if (directory == null)
- {
- // just use the file-spec as-is
- directory = fileSpec;
- }
- else if ((directory.Length > 0) && !EndsWithSlash(directory))
- {
- // restore trailing slash if Path.GetDirectoryName has removed it (this happens with non-root directories)
- directory += Path.DirectorySeparatorChar;
- }
-
- return directory;
- }
-
- ///
- /// Determines whether the given assembly file name has one of the listed extensions.
- ///
- /// The name of the file
- /// Array of extensions to consider.
- ///
- internal static bool HasExtension(string fileName, string[] allowedExtensions)
- {
- Debug.Assert(allowedExtensions?.Length > 0);
-
- // Easiest way to invoke invalid path chars
- // check, which callers are relying on.
- if (Path.HasExtension(fileName))
- {
- foreach (string extension in allowedExtensions)
- {
- Debug.Assert(!String.IsNullOrEmpty(extension) && extension[0] == '.');
-
- if (fileName.EndsWith(extension, PathComparison))
- {
- return true;
- }
- }
- }
-
- return false;
- }
-
- // ISO 8601 Universal time with sortable format
- internal const string FileTimeFormat = "yyyy'-'MM'-'dd HH':'mm':'ss'.'fffffff";
-
- ///
- /// Determines the full path for the given file-spec.
- /// ASSUMES INPUT IS STILL ESCAPED
- ///
- /// The file spec to get the full path of.
- ///
- /// full path
- internal static string GetFullPath(string fileSpec, string currentDirectory)
- {
- // Sending data out of the engine into the filesystem, so time to unescape.
- fileSpec = FixFilePath(EscapingUtilities.UnescapeAll(fileSpec));
-
- // Data coming back from the filesystem into the engine, so time to escape it back.
- string fullPath = EscapingUtilities.Escape(NormalizePath(Path.Combine(currentDirectory, fileSpec)));
-
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && !EndsWithSlash(fullPath))
- {
- if (FileUtilitiesRegex.DrivePattern.IsMatch(fileSpec) ||
- FileUtilitiesRegex.UNCPattern.IsMatch(fullPath))
- {
- // append trailing slash if Path.GetFullPath failed to (this happens with drive-specs and UNC shares)
- fullPath += Path.DirectorySeparatorChar;
- }
- }
-
- return fullPath;
- }
-
- ///
- /// A variation of Path.GetFullPath that will return the input value
- /// instead of throwing any IO exception.
- /// Useful to get a better path for an error message, without the risk of throwing
- /// if the error message was itself caused by the path being invalid!
- ///
- internal static string GetFullPathNoThrow(string path)
- {
- try
- {
- path = NormalizePath(path);
- }
- catch (Exception ex) when (ExceptionHandling.IsIoRelatedException(ex))
- {
- }
-
- return path;
- }
-
- ///
- /// Compare if two paths, relative to the given currentDirectory are equal.
- /// Does not throw IO exceptions. See
- ///
- ///
- ///
- ///
- ///
- ///
- internal static bool ComparePathsNoThrow(string first, string second, string currentDirectory, bool alwaysIgnoreCase = false)
- {
- StringComparison pathComparison = alwaysIgnoreCase ? StringComparison.OrdinalIgnoreCase : PathComparison;
- // perf: try comparing the bare strings first
- if (string.Equals(first, second, pathComparison))
- {
- return true;
- }
-
- var firstFullPath = NormalizePathForComparisonNoThrow(first, currentDirectory);
- var secondFullPath = NormalizePathForComparisonNoThrow(second, currentDirectory);
-
- return string.Equals(firstFullPath, secondFullPath, pathComparison);
- }
-
- ///
- /// Normalizes a path for path comparison
- /// Does not throw IO exceptions. See
- ///
- ///
- internal static string NormalizePathForComparisonNoThrow(string path, string currentDirectory)
- {
- // file is invalid, return early to avoid triggering an exception
- if (PathIsInvalid(path))
- {
- return path;
- }
-
- var normalizedPath = path.NormalizeForPathComparison();
- var fullPath = GetFullPathNoThrow(Path.Combine(currentDirectory, normalizedPath));
-
- return fullPath;
- }
-
- internal static bool PathIsInvalid(string path)
- {
- if (path.IndexOfAny(InvalidPathChars) >= 0)
- {
- return true;
- }
-
- // Path.GetFileName does not react well to malformed filenames.
- // For example, Path.GetFileName("a/b/foo:bar") returns bar instead of foo:bar
- // It also throws exceptions on illegal path characters
- var lastDirectorySeparator = path.LastIndexOfAny(Slashes);
-
- return path.IndexOfAny(InvalidFileNameChars, lastDirectorySeparator >= 0 ? lastDirectorySeparator + 1 : 0) >= 0;
- }
-
- ///
- /// A variation on File.Delete that will throw ExceptionHandling.NotExpectedException exceptions
- ///
- internal static void DeleteNoThrow(string path)
- {
- try
- {
- File.Delete(FixFilePath(path));
- }
- catch (Exception ex) when (ExceptionHandling.IsIoRelatedException(ex))
- {
- }
- }
-
- ///
- /// A variation on Directory.Delete that will throw ExceptionHandling.NotExpectedException exceptions
- ///
- [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.Int32.TryParse(System.String,System.Int32@)", Justification = "We expect the out value to be 0 if the parse fails and compensate accordingly")]
- internal static void DeleteDirectoryNoThrow(string path, bool recursive, int retryCount = 0, int retryTimeOut = 0)
- {
- // Try parse will set the out parameter to 0 if the string passed in is null, or is outside the range of an int.
- if (!int.TryParse(Environment.GetEnvironmentVariable("MSBUILDDIRECTORYDELETERETRYCOUNT"), out retryCount))
- {
- retryCount = 0;
- }
-
- if (!int.TryParse(Environment.GetEnvironmentVariable("MSBUILDDIRECTORYDELETRETRYTIMEOUT"), out retryTimeOut))
- {
- retryTimeOut = 0;
- }
-
- retryCount = retryCount < 1 ? 2 : retryCount;
- retryTimeOut = retryTimeOut < 1 ? 500 : retryTimeOut;
-
- path = FixFilePath(path);
-
- for (int i = 0; i < retryCount; i++)
- {
- try
- {
- if (Directory.Exists(path))
- {
- Directory.Delete(path, recursive);
- break;
- }
- }
- catch (Exception ex) when (ExceptionHandling.IsIoRelatedException(ex))
- {
- }
-
- if (i + 1 < retryCount) // should not wait for the final iteration since we not gonna check anyway
- {
- Thread.Sleep(retryTimeOut);
- }
- }
- }
-
- ///
- /// Deletes a directory, ensuring that Directory.Delete does not get a path ending in a slash.
- ///
- ///
- /// This is a workaround for https://github.com/dotnet/corefx/issues/3780, which clashed with a common
- /// pattern in our tests.
- ///
- internal static void DeleteWithoutTrailingBackslash(string path, bool recursive = false)
- {
- // Some tests (such as FileMatcher and Evaluation tests) were failing with an UnauthorizedAccessException or directory not empty.
- // This retry logic works around that issue.
- const int NUM_TRIES = 3;
- for (int i = 0; i < NUM_TRIES; i++)
- {
- try
- {
- Directory.Delete(EnsureNoTrailingSlash(path), recursive);
-
- // If we got here, the directory was successfully deleted
- return;
- }
- catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException)
- {
- if (i == NUM_TRIES - 1)
- {
- //var files = Directory.GetFiles(path, "*.*", SearchOption.AllDirectories);
- //string fileString = string.Join(Environment.NewLine, files);
- //string message = $"Unable to delete directory '{path}'. Contents:" + Environment.NewLine + fileString;
- //throw new IOException(message, ex);
- throw;
- }
- }
-
- Thread.Sleep(10);
- }
- }
-
- ///
- /// Gets a file info object for the specified file path. If the file path
- /// is invalid, or is a directory, or cannot be accessed, or does not exist,
- /// it returns null rather than throwing or returning a FileInfo around a non-existent file.
- /// This allows it to be called where File.Exists() (which never throws, and returns false
- /// for directories) was called - but with the advantage that a FileInfo object is returned
- /// that can be queried (e.g., for LastWriteTime) without hitting the disk again.
- ///
- ///
- /// FileInfo around path if it is an existing /file/, else null
- internal static FileInfo GetFileInfoNoThrow(string filePath)
- {
- filePath = AttemptToShortenPath(filePath);
-
- FileInfo fileInfo;
-
- try
- {
- fileInfo = new FileInfo(filePath);
- }
- catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e))
- {
- // Invalid or inaccessible path: treat as if nonexistent file, just as File.Exists does
- return null;
- }
-
- if (fileInfo.Exists)
- {
- // It's an existing file
- return fileInfo;
- }
- else
- {
- // Nonexistent, or existing but a directory, just as File.Exists behaves
- return null;
- }
- }
-
- ///
- /// Returns if the directory exists
- ///
- /// Full path to the directory in the filesystem
- ///
- internal static bool DirectoryExistsNoThrow(string fullPath)
- {
- fullPath = AttemptToShortenPath(fullPath);
-
- try
- {
- return Directory.Exists(fullPath);
- }
- catch
- {
- return false;
- }
- }
-
- ///
- /// Returns if the directory exists
- ///
- /// Full path to the file in the filesystem
- ///
- internal static bool FileExistsNoThrow(string fullPath)
- {
- fullPath = AttemptToShortenPath(fullPath);
-
- try
- {
- return File.Exists(fullPath);
- }
- catch
- {
- return false;
- }
- }
-
- ///
- /// If there is a directory or file at the specified path, returns true.
- /// Otherwise, returns false.
- /// Does not throw IO exceptions, to match Directory.Exists and File.Exists.
- /// Unlike calling each of those in turn it only accesses the disk once, which is faster.
- ///
- internal static bool FileOrDirectoryExistsNoThrow(string fullPath)
- {
- fullPath = AttemptToShortenPath(fullPath);
-
- try
- {
- return File.Exists(fullPath) || Directory.Exists(fullPath);
- }
- catch
- {
- return false;
- }
- }
-
- ///
- /// This method returns true if the specified filename is a solution file (.sln) or
- /// solution filter file (.slnf); otherwise, it returns false.
- ///
- ///
- /// Solution filters are included because they are a thin veneer over solutions, just
- /// with a more limited set of projects to build, and should be treated the same way.
- ///
- internal static bool IsSolutionFilename(string filename)
- {
- return HasExtension(filename, ".sln") || HasExtension(filename, ".slnf");
- }
-
- internal static bool IsSolutionFilterFilename(string filename)
- {
- return HasExtension(filename, ".slnf");
- }
-
- ///
- /// Returns true if the specified filename is a VC++ project file, otherwise returns false
- ///
- internal static bool IsVCProjFilename(string filename)
- {
- return HasExtension(filename, ".vcproj");
- }
-
- internal static bool IsDspFilename(string filename)
- {
- return HasExtension(filename, ".dsp");
- }
-
- ///
- /// Returns true if the specified filename is a metaproject file (.metaproj), otherwise false.
- ///
- internal static bool IsMetaprojectFilename(string filename)
- {
- return HasExtension(filename, ".metaproj");
- }
-
- internal static bool IsBinaryLogFilename(string filename)
- {
- return HasExtension(filename, ".binlog");
- }
-
- private static bool HasExtension(string filename, string extension)
- {
- if (String.IsNullOrEmpty(filename))
- return false;
-
- return filename.EndsWith(extension, PathComparison);
- }
-
- ///
- /// Given the absolute location of a file, and a disc location, returns relative file path to that disk location.
- /// Throws UriFormatException.
- ///
- ///
- /// The base path we want to be relative to. Must be absolute.
- /// Should not include a filename as the last segment will be interpreted as a directory.
- ///
- ///
- /// The path we need to make relative to basePath. The path can be either absolute path or a relative path in which case it is relative to the base path.
- /// If the path cannot be made relative to the base path (for example, it is on another drive), it is returned verbatim.
- /// If the basePath is an empty string, returns the path.
- ///
- /// relative path (can be the full path)
- internal static string MakeRelative(string basePath, string path)
- {
- ErrorUtilities.VerifyThrowArgumentNull(basePath, nameof(basePath));
- ErrorUtilities.VerifyThrowArgumentLength(path, nameof(path));
-
- string fullBase = Path.GetFullPath(basePath);
- string fullPath = Path.GetFullPath(path);
-
- string[] splitBase = fullBase.Split(MSBuildConstants.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries);
- string[] splitPath = fullPath.Split(MSBuildConstants.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries);
-
- ErrorUtilities.VerifyThrow(splitPath.Length > 0, "Cannot call MakeRelative on a path of only slashes.");
-
- // On a mac, the path could start with any number of slashes and still be valid. We have to check them all.
- int indexOfFirstNonSlashChar = 0;
- while (path[indexOfFirstNonSlashChar] == Path.DirectorySeparatorChar)
- {
- indexOfFirstNonSlashChar++;
- }
- if (path.IndexOf(splitPath[0]) != indexOfFirstNonSlashChar)
- {
- // path was already relative so just return it
- return FixFilePath(path);
- }
-
- int index = 0;
- while (index < splitBase.Length && index < splitPath.Length && splitBase[index].Equals(splitPath[index], PathComparison))
- {
- index++;
- }
-
- if (index == splitBase.Length && index == splitPath.Length)
- {
- return ".";
- }
-
- // If the paths have no component in common, the only valid relative path is the full path.
- if (index == 0)
- {
- return fullPath;
- }
-
- StringBuilder sb = StringBuilderCache.Acquire();
-
- for (int i = index; i < splitBase.Length; i++)
- {
- sb.Append("..").Append(Path.DirectorySeparatorChar);
- }
- for (int i = index; i < splitPath.Length; i++)
- {
- sb.Append(splitPath[i]).Append(Path.DirectorySeparatorChar);
- }
-
- if (fullPath[fullPath.Length - 1] != Path.DirectorySeparatorChar)
- {
- sb.Length--;
- }
-
- return StringBuilderCache.GetStringAndRelease(sb);
- }
-
- ///
- /// Helper function to create an Uri object from path.
- ///
- /// path string
- /// uri object
- private static Uri CreateUriFromPath(string path)
- {
- ErrorUtilities.VerifyThrowArgumentLength(path, nameof(path));
-
- Uri pathUri;
-
- // Try absolute first, then fall back on relative, otherwise it
- // makes some absolute UNC paths like (\\foo\bar) relative ...
- if (!Uri.TryCreate(path, UriKind.Absolute, out pathUri))
- {
- pathUri = new Uri(path, UriKind.Relative);
- }
-
- return pathUri;
- }
-
- ///
- /// Normalizes the path if and only if it is longer than max path,
- /// or would be if rooted by the current directory.
- /// This may make it shorter by removing ".."'s.
- ///
- internal static string AttemptToShortenPath(string path)
- {
- if (IsPathTooLong(path) || IsPathTooLongIfRooted(path))
- {
- // Attempt to make it shorter -- perhaps there are some \..\ elements
- path = GetFullPathNoThrow(path);
- }
- return FixFilePath(path);
- }
-
- private static bool IsPathTooLong(string path)
- {
- // >= not > because MAX_PATH assumes a trailing null
- return path.Length >= NativeMethods.MaxPath;
- }
-
- private static bool IsPathTooLongIfRooted(string path)
- {
- bool hasMaxPath = NativeMethods.HasMaxPath;
- int maxPath = NativeMethods.MaxPath;
- // >= not > because MAX_PATH assumes a trailing null
- return hasMaxPath && !IsRootedNoThrow(path) && NativeMethods.GetCurrentDirectory().Length + path.Length + 1 /* slash */ >= maxPath;
- }
-
- ///
- /// A variation of Path.IsRooted that not throw any IO exception.
- ///
- private static bool IsRootedNoThrow(string path)
- {
- try
- {
- return Path.IsPathRooted(FixFilePath(path));
- }
- catch (Exception ex) when (ExceptionHandling.IsIoRelatedException(ex))
- {
- return false;
- }
- }
-
- ///
- /// Get the folder N levels above the given. Will stop and return current path when rooted.
- ///
- /// Path to get the folder above.
- /// Number of levels up to walk.
- /// Full path to the folder N levels above the path.
- internal static string GetFolderAbove(string path, int count = 1)
- {
- if (count < 1)
- return path;
-
- var parent = Directory.GetParent(path);
-
- while (count > 1 && parent?.Parent != null)
- {
- parent = parent.Parent;
- count--;
- }
-
- return parent?.FullName ?? path;
- }
-
- ///
- /// Combine multiple paths. Should only be used when compiling against .NET 2.0.
- ///
- /// Only use in .NET 2.0. Otherwise, use System.IO.Path.Combine(...)
- ///
- ///
- /// Root path.
- /// Paths to concatenate.
- /// Combined path.
- internal static string CombinePaths(string root, params string[] paths)
- {
- ErrorUtilities.VerifyThrowArgumentNull(root, nameof(root));
- ErrorUtilities.VerifyThrowArgumentNull(paths, nameof(paths));
-
- return paths.Aggregate(root, Path.Combine);
- }
-
- internal static string TrimTrailingSlashes(this string s)
- {
- return s.TrimEnd(Slashes);
- }
-
- ///
- /// Replace all backward slashes to forward slashes
- ///
- internal static string ToSlash(this string s)
- {
- return s.Replace('\\', '/');
- }
-
- internal static string ToBackslash(this string s)
- {
- return s.Replace('/', '\\');
- }
-
- ///
- /// Ensure all slashes are the current platform's slash
- ///
- ///
- ///
- internal static string ToPlatformSlash(this string s)
- {
- var separator = Path.DirectorySeparatorChar;
-
- return s.Replace(separator == '/' ? '\\' : '/', separator);
- }
-
- internal static string WithTrailingSlash(this string s)
- {
- return EnsureTrailingSlash(s);
- }
-
- internal static string NormalizeForPathComparison(this string s) => s.ToPlatformSlash().TrimTrailingSlashes();
-
- // TODO: assumption on file system case sensitivity: https://github.com/dotnet/msbuild/issues/781
- internal static bool PathsEqual(string path1, string path2)
- {
- if (path1 == null && path2 == null)
- {
- return true;
- }
- if (path1 == null || path2 == null)
- {
- return false;
- }
-
- var endA = path1.Length - 1;
- var endB = path2.Length - 1;
-
- // Trim trailing slashes
- for (var i = endA; i >= 0; i--)
- {
- var c = path1[i];
- if (c == '/' || c == '\\')
- {
- endA--;
- }
- else
- {
- break;
- }
- }
-
- for (var i = endB; i >= 0; i--)
- {
- var c = path2[i];
- if (c == '/' || c == '\\')
- {
- endB--;
- }
- else
- {
- break;
- }
- }
-
- if (endA != endB)
- {
- // Lengths not the same
- return false;
- }
-
- for (var i = 0; i <= endA; i++)
- {
- var charA = (uint)path1[i];
- var charB = (uint)path2[i];
-
- if ((charA | charB) > 0x7F)
- {
- // Non-ascii chars move to non fast path
- return PathsEqualNonAscii(path1, path2, i, endA - i + 1);
- }
-
- // uppercase both chars - notice that we need just one compare per char
- if ((uint)(charA - 'a') <= (uint)('z' - 'a')) charA -= 0x20;
- if ((uint)(charB - 'a') <= (uint)('z' - 'a')) charB -= 0x20;
-
- // Set path delimiters the same
- if (charA == '\\')
- {
- charA = '/';
- }
- if (charB == '\\')
- {
- charB = '/';
- }
-
- if (charA != charB)
- {
- return false;
- }
- }
-
- return true;
- }
-
- internal static StreamWriter OpenWrite(string path, bool append, Encoding encoding = null)
- {
- const int DefaultFileStreamBufferSize = 4096;
- FileMode mode = append ? FileMode.Append : FileMode.Create;
- Stream fileStream = new FileStream(path, mode, FileAccess.Write, FileShare.Read, DefaultFileStreamBufferSize, FileOptions.SequentialScan);
- if (encoding == null)
- {
- return new StreamWriter(fileStream);
- }
- else
- {
- return new StreamWriter(fileStream, encoding);
- }
- }
-
- internal static StreamReader OpenRead(string path, Encoding encoding = null, bool detectEncodingFromByteOrderMarks = true)
- {
- const int DefaultFileStreamBufferSize = 4096;
- Stream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, DefaultFileStreamBufferSize, FileOptions.SequentialScan);
- if (encoding == null)
- {
- return new StreamReader(fileStream);
- }
- else
- {
- return new StreamReader(fileStream, encoding, detectEncodingFromByteOrderMarks);
- }
- }
-
- ///
- /// Locate a file in either the directory specified or a location in the
- /// directory structure above that directory.
- ///
- internal static string GetDirectoryNameOfFileAbove(string startingDirectory, string fileName)
- {
- // Canonicalize our starting location
- string lookInDirectory = GetFullPath(startingDirectory);
-
- do
- {
- // Construct the path that we will use to test against
- string 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
- {
- // 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;
- }
-
- ///
- /// Searches for a file based on the specified starting directory.
- ///
- /// The file to search for.
- /// An optional directory to start the search in. The default location is the directory
- /// of the file containing the property function.
- /// The full path of the file if it is found, otherwise an empty string.
- internal static string GetPathOfFileAbove(string file, string startingDirectory)
- {
- // This method does not accept a path, only a file name
- if (file.Any(i => i.Equals(Path.DirectorySeparatorChar) || i.Equals(Path.AltDirectorySeparatorChar)))
- {
- ErrorUtilities.ThrowArgument("InvalidGetPathOfFileAboveParameter", file);
- }
-
- // Search for a directory that contains that file
- string directoryName = GetDirectoryNameOfFileAbove(startingDirectory, file);
-
- return String.IsNullOrEmpty(directoryName) ? String.Empty : NormalizePath(directoryName, file);
- }
-
- internal static void EnsureDirectoryExists(string directoryPath)
- {
- if (directoryPath != null && !Directory.Exists(directoryPath))
- {
- Directory.CreateDirectory(directoryPath);
- }
- }
-
- // Method is simple set of function calls and may inline;
- // we don't want it inlining into the tight loop that calls it as an exit case,
- // so mark as non-inlining
- [MethodImpl(MethodImplOptions.NoInlining)]
- private static bool PathsEqualNonAscii(string strA, string strB, int i, int length)
- {
- if (string.Compare(strA, i, strB, i, length, StringComparison.OrdinalIgnoreCase) == 0)
- {
- return true;
- }
-
- var slash1 = strA.ToSlash();
- var slash2 = strB.ToSlash();
-
- if (string.Compare(slash1, i, slash2, i, length, StringComparison.OrdinalIgnoreCase) == 0)
- {
- return true;
- }
-
- return false;
- }
-
-#if !CLR2COMPATIBILITY
- ///
- /// Clears the file existence cache.
- ///
- internal static void ClearFileExistenceCache()
- {
- FileExistenceCache.Clear();
- }
-#endif
- }
-}
diff --git a/src/Ionide.ProjInfo.Sln/Ionide.ProjInfo.Sln.csproj b/src/Ionide.ProjInfo.Sln/Ionide.ProjInfo.Sln.csproj
deleted file mode 100644
index 44c668aa..00000000
--- a/src/Ionide.ProjInfo.Sln/Ionide.ProjInfo.Sln.csproj
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
- net8.0
- $(TargetFrameworks);net9.0
- Parse sln files
- FULL_SLN_PARSER;STANDALONEBUILD;$(DefineConstants)
- true
-
-
diff --git a/src/Ionide.ProjInfo.Sln/NativeMethods.cs b/src/Ionide.ProjInfo.Sln/NativeMethods.cs
deleted file mode 100644
index 7cf10fa9..00000000
--- a/src/Ionide.ProjInfo.Sln/NativeMethods.cs
+++ /dev/null
@@ -1,1625 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
-using System.IO;
-using System.Linq;
-using System.Reflection;
-using System.Runtime.InteropServices;
-using System.Text;
-using System.Threading;
-
-using Ionide.ProjInfo.Sln.Shared;
-using Microsoft.Win32;
-using Microsoft.Win32.SafeHandles;
-
-using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME;
-
-namespace Ionide.ProjInfo.Sln.Shared
-{
- internal static class NativeMethods
- {
- #region Constants
-
- internal const uint ERROR_INSUFFICIENT_BUFFER = 0x8007007A;
- internal const uint STARTUP_LOADER_SAFEMODE = 0x10;
- internal const uint S_OK = 0x0;
- internal const uint S_FALSE = 0x1;
- internal const uint ERROR_ACCESS_DENIED = 0x5;
- internal const uint ERROR_FILE_NOT_FOUND = 0x80070002;
- internal const uint FUSION_E_PRIVATE_ASM_DISALLOWED = 0x80131044; // Tried to find unsigned assembly in GAC
- internal const uint RUNTIME_INFO_DONT_SHOW_ERROR_DIALOG = 0x40;
- internal const uint FILE_TYPE_CHAR = 0x0002;
- internal const Int32 STD_OUTPUT_HANDLE = -11;
- internal const uint RPC_S_CALLPENDING = 0x80010115;
- internal const uint E_ABORT = (uint)0x80004004;
-
- internal const int FILE_ATTRIBUTE_READONLY = 0x00000001;
- internal const int FILE_ATTRIBUTE_DIRECTORY = 0x00000010;
- internal const int FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400;
-
- ///
- /// Default buffer size to use when dealing with the Windows API.
- ///
- internal const int MAX_PATH = 260;
-
- private const string kernel32Dll = "kernel32.dll";
- private const string mscoreeDLL = "mscoree.dll";
-
- private const string WINDOWS_FILE_SYSTEM_REGISTRY_KEY = @"SYSTEM\CurrentControlSet\Control\FileSystem";
- private const string WINDOWS_LONG_PATHS_ENABLED_VALUE_NAME = "LongPathsEnabled";
-
- internal static DateTime MinFileDate { get; } = DateTime.FromFileTimeUtc(0);
-
-#if FEATURE_HANDLEREF
- internal static HandleRef NullHandleRef = new HandleRef(null, IntPtr.Zero);
-#endif
-
- internal static IntPtr NullIntPtr = new IntPtr(0);
-
- // As defined in winnt.h:
- internal const ushort PROCESSOR_ARCHITECTURE_INTEL = 0;
- internal const ushort PROCESSOR_ARCHITECTURE_ARM = 5;
- internal const ushort PROCESSOR_ARCHITECTURE_IA64 = 6;
- internal const ushort PROCESSOR_ARCHITECTURE_AMD64 = 9;
- internal const ushort PROCESSOR_ARCHITECTURE_ARM64 = 12;
-
- internal const uint INFINITE = 0xFFFFFFFF;
- internal const uint WAIT_ABANDONED_0 = 0x00000080;
- internal const uint WAIT_OBJECT_0 = 0x00000000;
- internal const uint WAIT_TIMEOUT = 0x00000102;
-
-#if FEATURE_CHARSET_AUTO
- internal const CharSet AutoOrUnicode = CharSet.Auto;
-#else
- internal const CharSet AutoOrUnicode = CharSet.Unicode;
-#endif
-
- #endregion
-
- #region Enums
-
- private enum PROCESSINFOCLASS : int
- {
- ProcessBasicInformation = 0,
- ProcessQuotaLimits,
- ProcessIoCounters,
- ProcessVmCounters,
- ProcessTimes,
- ProcessBasePriority,
- ProcessRaisePriority,
- ProcessDebugPort,
- ProcessExceptionPort,
- ProcessAccessToken,
- ProcessLdtInformation,
- ProcessLdtSize,
- ProcessDefaultHardErrorMode,
- ProcessIoPortHandlers, // Note: this is kernel mode only
- ProcessPooledUsageAndLimits,
- ProcessWorkingSetWatch,
- ProcessUserModeIOPL,
- ProcessEnableAlignmentFaultFixup,
- ProcessPriorityClass,
- ProcessWx86Information,
- ProcessHandleCount,
- ProcessAffinityMask,
- ProcessPriorityBoost,
- MaxProcessInfoClass
- };
-
- private enum eDesiredAccess : int
- {
- DELETE = 0x00010000,
- READ_CONTROL = 0x00020000,
- WRITE_DAC = 0x00040000,
- WRITE_OWNER = 0x00080000,
- SYNCHRONIZE = 0x00100000,
- STANDARD_RIGHTS_ALL = 0x001F0000,
-
- PROCESS_TERMINATE = 0x0001,
- PROCESS_CREATE_THREAD = 0x0002,
- PROCESS_SET_SESSIONID = 0x0004,
- PROCESS_VM_OPERATION = 0x0008,
- PROCESS_VM_READ = 0x0010,
- PROCESS_VM_WRITE = 0x0020,
- PROCESS_DUP_HANDLE = 0x0040,
- PROCESS_CREATE_PROCESS = 0x0080,
- PROCESS_SET_QUOTA = 0x0100,
- PROCESS_SET_INFORMATION = 0x0200,
- PROCESS_QUERY_INFORMATION = 0x0400,
- PROCESS_ALL_ACCESS = SYNCHRONIZE | 0xFFF
- }
-#pragma warning disable 0649, 0169
- internal enum LOGICAL_PROCESSOR_RELATIONSHIP
- {
- RelationProcessorCore,
- RelationNumaNode,
- RelationCache,
- RelationProcessorPackage,
- RelationGroup,
- RelationAll = 0xffff
- }
- internal struct SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX
- {
- public LOGICAL_PROCESSOR_RELATIONSHIP Relationship;
- public uint Size;
- public PROCESSOR_RELATIONSHIP Processor;
- }
- [StructLayout(LayoutKind.Sequential)]
- internal unsafe struct PROCESSOR_RELATIONSHIP
- {
- public byte Flags;
- private byte EfficiencyClass;
- private fixed byte Reserved[20];
- public ushort GroupCount;
- public IntPtr GroupInfo;
- }
-#pragma warning restore 0169, 0149
-
- ///
- /// Flags for CoWaitForMultipleHandles
- ///
- [Flags]
- public enum COWAIT_FLAGS : int
- {
- ///
- /// Exit when a handle is signaled.
- ///
- COWAIT_NONE = 0,
-
- ///
- /// Exit when all handles are signaled AND a message is received.
- ///
- COWAIT_WAITALL = 0x00000001,
-
- ///
- /// Exit when an RPC call is serviced.
- ///
- COWAIT_ALERTABLE = 0x00000002
- }
-
- ///
- /// Processor architecture values
- ///
- internal enum ProcessorArchitectures
- {
- // Intel 32 bit
- X86,
-
- // AMD64 64 bit
- X64,
-
- // Itanium 64
- IA64,
-
- // ARM
- ARM,
-
- // ARM64
- ARM64,
-
- // Who knows
- Unknown
- }
-
- #endregion
-
- #region Structs
-
- ///
- /// Structure that contain information about the system on which we are running
- ///
- [StructLayout(LayoutKind.Sequential)]
- internal struct SYSTEM_INFO
- {
- // This is a union of a DWORD and a struct containing 2 WORDs.
- internal ushort wProcessorArchitecture;
- internal ushort wReserved;
-
- internal uint dwPageSize;
- internal IntPtr lpMinimumApplicationAddress;
- internal IntPtr lpMaximumApplicationAddress;
- internal IntPtr dwActiveProcessorMask;
- internal uint dwNumberOfProcessors;
- internal uint dwProcessorType;
- internal uint dwAllocationGranularity;
- internal ushort wProcessorLevel;
- internal ushort wProcessorRevision;
- }
-
- ///
- /// Wrap the intptr returned by OpenProcess in a safe handle.
- ///
- internal class SafeProcessHandle : SafeHandleZeroOrMinusOneIsInvalid
- {
- // Create a SafeHandle, informing the base class
- // that this SafeHandle instance "owns" the handle,
- // and therefore SafeHandle should call
- // our ReleaseHandle method when the SafeHandle
- // is no longer in use
- private SafeProcessHandle() : base(true)
- {
- }
- protected override bool ReleaseHandle()
- {
- return CloseHandle(handle);
- }
- }
-
- ///
- /// Contains information about the current state of both physical and virtual memory, including extended memory
- ///
- [StructLayout(LayoutKind.Sequential, CharSet = AutoOrUnicode)]
- internal class MemoryStatus
- {
- ///
- /// Initializes a new instance of the class.
- ///
- public MemoryStatus()
- {
-#if (CLR2COMPATIBILITY)
- _length = (uint)Marshal.SizeOf(typeof(MemoryStatus));
-#else
- _length = (uint)Marshal.SizeOf();
-#endif
- }
-
- ///
- /// Size of the structure, in bytes. You must set this member before calling GlobalMemoryStatusEx.
- ///
- private uint _length;
-
- ///
- /// Number between 0 and 100 that specifies the approximate percentage of physical
- /// memory that is in use (0 indicates no memory use and 100 indicates full memory use).
- ///
- public uint MemoryLoad;
-
- ///
- /// Total size of physical memory, in bytes.
- ///
- public ulong TotalPhysical;
-
- ///
- /// Size of physical memory available, in bytes.
- ///
- public ulong AvailablePhysical;
-
- ///
- /// Size of the committed memory limit, in bytes. This is physical memory plus the
- /// size of the page file, minus a small overhead.
- ///
- public ulong TotalPageFile;
-
- ///
- /// Size of available memory to commit, in bytes. The limit is ullTotalPageFile.
- ///
- public ulong AvailablePageFile;
-
- ///
- /// Total size of the user mode portion of the virtual address space of the calling process, in bytes.
- ///
- public ulong TotalVirtual;
-
- ///
- /// Size of unreserved and uncommitted memory in the user mode portion of the virtual
- /// address space of the calling process, in bytes.
- ///
- public ulong AvailableVirtual;
-
- ///
- /// Size of unreserved and uncommitted memory in the extended portion of the virtual
- /// address space of the calling process, in bytes.
- ///
- public ulong AvailableExtendedVirtual;
- }
-
- [StructLayout(LayoutKind.Sequential)]
- private struct PROCESS_BASIC_INFORMATION
- {
- public uint ExitStatus;
- public IntPtr PebBaseAddress;
- public UIntPtr AffinityMask;
- public int BasePriority;
- public UIntPtr UniqueProcessId;
- public UIntPtr InheritedFromUniqueProcessId;
-
- public uint Size
- {
- get
- {
- unsafe
- {
- return (uint)sizeof(PROCESS_BASIC_INFORMATION);
- }
- }
- }
- };
-
- ///
- /// Contains information about a file or directory; used by GetFileAttributesEx.
- ///
- [StructLayout(LayoutKind.Sequential)]
- public struct WIN32_FILE_ATTRIBUTE_DATA
- {
- internal int fileAttributes;
- internal uint ftCreationTimeLow;
- internal uint ftCreationTimeHigh;
- internal uint ftLastAccessTimeLow;
- internal uint ftLastAccessTimeHigh;
- internal uint ftLastWriteTimeLow;
- internal uint ftLastWriteTimeHigh;
- internal uint fileSizeHigh;
- internal uint fileSizeLow;
- }
-
- ///
- /// Contains the security descriptor for an object and specifies whether
- /// the handle retrieved by specifying this structure is inheritable.
- ///
- [StructLayout(LayoutKind.Sequential)]
- internal class SecurityAttributes
- {
- public SecurityAttributes()
- {
-#if (CLR2COMPATIBILITY)
- _nLength = (uint)Marshal.SizeOf(typeof(SecurityAttributes));
-#else
- _nLength = (uint)Marshal.SizeOf();
-#endif
- }
-
- private uint _nLength;
-
- public IntPtr lpSecurityDescriptor;
-
- public bool bInheritHandle;
- }
-
- private class SystemInformationData
- {
- ///
- /// Architecture as far as the current process is concerned.
- /// It's x86 in wow64 (native architecture is x64 in that case).
- /// Otherwise it's the same as the native architecture.
- ///
- public readonly ProcessorArchitectures ProcessorArchitectureType;
-
- ///
- /// Actual architecture of the system.
- ///
- public readonly ProcessorArchitectures ProcessorArchitectureTypeNative;
-
- ///
- /// Convert SYSTEM_INFO architecture values to the internal enum
- ///
- ///
- ///
- private static ProcessorArchitectures ConvertSystemArchitecture(ushort arch)
- {
- return arch switch
- {
- PROCESSOR_ARCHITECTURE_INTEL => ProcessorArchitectures.X86,
- PROCESSOR_ARCHITECTURE_AMD64 => ProcessorArchitectures.X64,
- PROCESSOR_ARCHITECTURE_ARM => ProcessorArchitectures.ARM,
- PROCESSOR_ARCHITECTURE_IA64 => ProcessorArchitectures.IA64,
- PROCESSOR_ARCHITECTURE_ARM64 => ProcessorArchitectures.ARM64,
- _ => ProcessorArchitectures.Unknown,
- };
- }
-
- ///
- /// Read system info values
- ///
- public SystemInformationData()
- {
- ProcessorArchitectureType = ProcessorArchitectures.Unknown;
- ProcessorArchitectureTypeNative = ProcessorArchitectures.Unknown;
-
- if (IsWindows)
- {
- var systemInfo = new SYSTEM_INFO();
-
- GetSystemInfo(ref systemInfo);
- ProcessorArchitectureType = ConvertSystemArchitecture(systemInfo.wProcessorArchitecture);
-
- GetNativeSystemInfo(ref systemInfo);
- ProcessorArchitectureTypeNative = ConvertSystemArchitecture(systemInfo.wProcessorArchitecture);
- }
- else
- {
- ProcessorArchitectures processorArchitecture = ProcessorArchitectures.Unknown;
-#if !NET35
- // Get the architecture from the runtime.
- processorArchitecture = RuntimeInformation.OSArchitecture switch
- {
- Architecture.Arm => ProcessorArchitectures.ARM,
- Architecture.Arm64 => ProcessorArchitectures.ARM64,
- Architecture.X64 => ProcessorArchitectures.X64,
- Architecture.X86 => ProcessorArchitectures.X86,
- _ => ProcessorArchitectures.Unknown,
- };
-#endif
- // Fall back to 'uname -m' to get the architecture.
- if (processorArchitecture == ProcessorArchitectures.Unknown)
- {
- try
- {
- // On Unix run 'uname -m' to get the architecture. It's common for Linux and Mac
- using (
- var proc =
- Process.Start(
- new ProcessStartInfo("uname")
- {
- Arguments = "-m",
- UseShellExecute = false,
- RedirectStandardOutput = true,
- CreateNoWindow = true
- }))
- {
- string arch = null;
- if (proc != null)
- {
- arch = proc.StandardOutput.ReadLine();
- proc.WaitForExit();
- }
-
- if (!string.IsNullOrEmpty(arch))
- {
- if (arch.StartsWith("x86_64", StringComparison.OrdinalIgnoreCase))
- {
- ProcessorArchitectureType = ProcessorArchitectures.X64;
- }
- else if (arch.StartsWith("ia64", StringComparison.OrdinalIgnoreCase))
- {
- ProcessorArchitectureType = ProcessorArchitectures.IA64;
- }
- else if (arch.StartsWith("arm", StringComparison.OrdinalIgnoreCase))
- {
- ProcessorArchitectureType = ProcessorArchitectures.ARM;
- }
- else if (arch.StartsWith("aarch64", StringComparison.OrdinalIgnoreCase))
- {
- ProcessorArchitectureType = ProcessorArchitectures.ARM64;
- }
- else if (arch.StartsWith("i", StringComparison.OrdinalIgnoreCase)
- && arch.EndsWith("86", StringComparison.OrdinalIgnoreCase))
- {
- ProcessorArchitectureType = ProcessorArchitectures.X86;
- }
- }
- }
- }
- catch
- {
- // Best effort: fall back to Unknown
- }
- }
-
- ProcessorArchitectureTypeNative = ProcessorArchitectureType = processorArchitecture;
- }
- }
- }
-
- public static int GetLogicalCoreCount()
- {
- int numberOfCpus = Environment.ProcessorCount;
-#if !MONO
- // .NET Core on Windows returns a core count limited to the current NUMA node
- // https://github.com/dotnet/runtime/issues/29686
- // so always double-check it.
- if (IsWindows
-#if NETFRAMEWORK
- // .NET Framework calls Windows APIs that have a core count limit (32/64 depending on process bitness).
- // So if we get a high core count on full framework, double-check it.
- && (numberOfCpus >= 32)
-#endif
- )
- {
- var result = GetLogicalCoreCountOnWindows();
- if (result != -1)
- {
- numberOfCpus = result;
- }
- }
-#endif
-
- return numberOfCpus;
- }
-
- ///
- /// Get the exact physical core count on Windows
- /// Useful for getting the exact core count in 32 bits processes,
- /// as Environment.ProcessorCount has a 32-core limit in that case.
- /// https://github.com/dotnet/runtime/blob/221ad5b728f93489655df290c1ea52956ad8f51c/src/libraries/System.Runtime.Extensions/src/System/Environment.Windows.cs#L171-L210
- ///
- private unsafe static int GetLogicalCoreCountOnWindows()
- {
- uint len = 0;
- const int ERROR_INSUFFICIENT_BUFFER = 122;
-
- if (!GetLogicalProcessorInformationEx(LOGICAL_PROCESSOR_RELATIONSHIP.RelationProcessorCore, IntPtr.Zero, ref len) &&
- Marshal.GetLastWin32Error() == ERROR_INSUFFICIENT_BUFFER)
- {
- // Allocate that much space
- var buffer = new byte[len];
- fixed (byte* bufferPtr = buffer)
- {
- // Call GetLogicalProcessorInformationEx with the allocated buffer
- if (GetLogicalProcessorInformationEx(LOGICAL_PROCESSOR_RELATIONSHIP.RelationProcessorCore, (IntPtr)bufferPtr, ref len))
- {
- // Walk each SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX in the buffer, where the Size of each dictates how
- // much space it's consuming. For each group relation, count the number of active processors in each of its group infos.
- int processorCount = 0;
- byte* ptr = bufferPtr;
- byte* endPtr = bufferPtr + len;
- while (ptr < endPtr)
- {
- var current = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)ptr;
- if (current->Relationship == LOGICAL_PROCESSOR_RELATIONSHIP.RelationProcessorCore)
- {
- // Flags is 0 if the core has a single logical proc, LTP_PC_SMT if more than one
- // for now, assume "more than 1" == 2, as it has historically been for hyperthreading
- processorCount += (current->Processor.Flags == 0) ? 1 : 2;
- }
- ptr += current->Size;
- }
- return processorCount;
- }
- }
- }
-
- return -1;
- }
-
- #endregion
-
- #region Member data
-
- internal static bool HasMaxPath => MaxPath == MAX_PATH;
-
- ///
- /// Gets the max path limit of the current OS.
- ///
- internal static int MaxPath
- {
- get
- {
- if (!IsMaxPathSet)
- {
- SetMaxPath();
- }
- return _maxPath;
- }
- }
-
- ///
- /// Cached value for MaxPath.
- ///
- private static int _maxPath;
-
- private static bool IsMaxPathSet { get; set; }
-
- private static readonly object MaxPathLock = new object();
-
- private static void SetMaxPath()
- {
- lock (MaxPathLock)
- {
- if (!IsMaxPathSet)
- {
- bool isMaxPathRestricted = IsMaxPathLegacyWindows();
- _maxPath = isMaxPathRestricted ? MAX_PATH : int.MaxValue;
- IsMaxPathSet = true;
- }
- }
- }
-
- internal static bool IsMaxPathLegacyWindows()
- {
- try
- {
- return IsWindows && !IsLongPathsEnabledRegistry();
- }
- catch
- {
- return true;
- }
- }
-
- // CA1416 warns about code that can only run on Windows, but we verified we're running on Windows before this.
- // This is the most reasonable way to resolve this part because other ways would require ifdef'ing on NET472.
-#pragma warning disable CA1416
- private static bool IsLongPathsEnabledRegistry()
- {
- return false;
- }
-#pragma warning restore CA1416
-
- ///
- /// Cached value for IsUnixLike (this method is called frequently during evaluation).
- ///
- private static readonly bool s_isUnixLike = IsLinux || IsOSX || IsBSD;
-
- ///
- /// Gets a flag indicating if we are running under a Unix-like system (Mac, Linux, etc.)
- ///
- internal static bool IsUnixLike
- {
- get { return s_isUnixLike; }
- }
-
- ///
- /// Gets a flag indicating if we are running under Linux
- ///
- internal static bool IsLinux
- {
-#if CLR2COMPATIBILITY
- get { return false; }
-#else
- get { return RuntimeInformation.IsOSPlatform(OSPlatform.Linux); }
-#endif
- }
-
- ///
- /// Gets a flag indicating if we are running under flavor of BSD (NetBSD, OpenBSD, FreeBSD)
- ///
- internal static bool IsBSD
- {
-#if CLR2COMPATIBILITY
- get { return false; }
-#else
- get
- {
- return RuntimeInformation.IsOSPlatform(OSPlatform.Create("FREEBSD")) ||
- RuntimeInformation.IsOSPlatform(OSPlatform.Create("NETBSD")) ||
- RuntimeInformation.IsOSPlatform(OSPlatform.Create("OPENBSD"));
- }
-#endif
- }
-
- private static readonly object IsMonoLock = new object();
-
- ///
- /// Gets a flag indicating if we are running under MONO
- ///
- internal static bool IsMono
- {
- get
- {
- return false;
- }
- }
-
-#if !CLR2COMPATIBILITY
- private static bool? _isWindows;
-#endif
-
- ///
- /// Gets a flag indicating if we are running under some version of Windows
- ///
- internal static bool IsWindows
- {
-#if CLR2COMPATIBILITY
- get { return true; }
-#else
- get
- {
- if (_isWindows == null)
- {
- _isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
- }
- return _isWindows.Value;
- }
-#endif
- }
-
-#if !CLR2COMPATIBILITY
- private static bool? _isOSX;
-#endif
-
- ///
- /// Gets a flag indicating if we are running under Mac OSX
- ///
- internal static bool IsOSX
- {
-#if CLR2COMPATIBILITY
- get { return false; }
-#else
- get
- {
- if (_isOSX == null)
- {
- _isOSX = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
- }
- return _isOSX.Value;
- }
-#endif
- }
-
- ///
- /// Gets a string for the current OS. This matches the OS env variable
- /// for Windows (Windows_NT).
- ///
- internal static string OSName
- {
- get { return IsWindows ? "Windows_NT" : "Unix"; }
- }
-
- ///
- /// OS name that can be used for the msbuildExtensionsPathSearchPaths element
- /// for a toolset
- ///
- internal static string GetOSNameForExtensionsPath()
- {
- return IsOSX ? "osx" : IsUnixLike ? "unix" : "windows";
- }
-
- internal static bool OSUsesCaseSensitivePaths
- {
- get { return IsLinux; }
- }
-
- ///
- /// System information, initialized when required.
- ///
- ///
- /// Initially implemented as , but
- /// that's .NET 4+, and this is used in MSBuildTaskHost.
- ///
- private static SystemInformationData SystemInformation
- {
- get
- {
- if (!_systemInformationInitialized)
- {
- lock (SystemInformationLock)
- {
- if (!_systemInformationInitialized)
- {
- _systemInformation = new SystemInformationData();
- _systemInformationInitialized = true;
- }
- }
- }
- return _systemInformation;
- }
- }
-
- private static SystemInformationData _systemInformation;
- private static bool _systemInformationInitialized;
- private static readonly object SystemInformationLock = new object();
-
- ///
- /// Architecture getter
- ///
- internal static ProcessorArchitectures ProcessorArchitecture => SystemInformation.ProcessorArchitectureType;
-
- ///
- /// Native architecture getter
- ///
- internal static ProcessorArchitectures ProcessorArchitectureNative => SystemInformation.ProcessorArchitectureTypeNative;
-
- #endregion
-
- #region Wrapper methods
-
- ///
- /// Really truly non pumping wait.
- /// Raw IntPtrs have to be used, because the marshaller does not support arrays of SafeHandle, only
- /// single SafeHandles.
- ///
- [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
- public static extern Int32 WaitForMultipleObjects(uint handle, IntPtr[] handles, bool waitAll, uint milliseconds);
-
- [DllImport("kernel32.dll", SetLastError = true)]
- internal static extern void GetSystemInfo(ref SYSTEM_INFO lpSystemInfo);
-
- [DllImport("kernel32.dll", SetLastError = true)]
- internal static extern void GetNativeSystemInfo(ref SYSTEM_INFO lpSystemInfo);
-
- [DllImport("kernel32.dll", SetLastError = true)]
- internal static extern bool GetLogicalProcessorInformationEx(LOGICAL_PROCESSOR_RELATIONSHIP RelationshipType, IntPtr Buffer, ref uint ReturnedLength);
-
- ///
- /// Get the last write time of the fullpath to a directory. If the pointed path is not a directory, or
- /// if the directory does not exist, then false is returned and fileModifiedTimeUtc is set DateTime.MinValue.
- ///
- /// Full path to the file in the filesystem
- /// The UTC last write time for the directory
- internal static bool GetLastWriteDirectoryUtcTime(string fullPath, out DateTime fileModifiedTimeUtc)
- {
- // This code was copied from the reference manager, if there is a bug fix in that code, see if the same fix should also be made
- // there
- if (IsWindows)
- {
- fileModifiedTimeUtc = DateTime.MinValue;
-
- WIN32_FILE_ATTRIBUTE_DATA data = new WIN32_FILE_ATTRIBUTE_DATA();
- bool success = GetFileAttributesEx(fullPath, 0, ref data);
- if (success)
- {
- if ((data.fileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0)
- {
- long dt = ((long)(data.ftLastWriteTimeHigh) << 32) | ((long)data.ftLastWriteTimeLow);
- fileModifiedTimeUtc = DateTime.FromFileTimeUtc(dt);
- }
- else
- {
- // Path does not point to a directory
- success = false;
- }
- }
-
- return success;
- }
-
- if (Directory.Exists(fullPath))
- {
- fileModifiedTimeUtc = Directory.GetLastWriteTimeUtc(fullPath);
- return true;
- }
- else
- {
- fileModifiedTimeUtc = DateTime.MinValue;
- return false;
- }
- }
-
- ///
- /// Takes the path and returns the short path
- ///
- internal static string GetShortFilePath(string path)
- {
- if (!IsWindows)
- {
- return path;
- }
-
- if (path != null)
- {
- int length = GetShortPathName(path, null, 0);
- int errorCode = Marshal.GetLastWin32Error();
-
- if (length > 0)
- {
- StringBuilder fullPathBuffer = new StringBuilder(length);
- length = GetShortPathName(path, fullPathBuffer, length);
- errorCode = Marshal.GetLastWin32Error();
-
- if (length > 0)
- {
- string fullPath = fullPathBuffer.ToString();
- path = fullPath;
- }
- }
-
- if (length == 0 && errorCode != 0)
- {
- ThrowExceptionForErrorCode(errorCode);
- }
- }
-
- return path;
- }
-
- ///
- /// Takes the path and returns a full path
- ///
- ///
- ///
- internal static string GetLongFilePath(string path)
- {
- if (IsUnixLike)
- {
- return path;
- }
-
- if (path != null)
- {
- int length = GetLongPathName(path, null, 0);
- int errorCode = Marshal.GetLastWin32Error();
-
- if (length > 0)
- {
- StringBuilder fullPathBuffer = new StringBuilder(length);
- length = GetLongPathName(path, fullPathBuffer, length);
- errorCode = Marshal.GetLastWin32Error();
-
- if (length > 0)
- {
- string fullPath = fullPathBuffer.ToString();
- path = fullPath;
- }
- }
-
- if (length == 0 && errorCode != 0)
- {
- ThrowExceptionForErrorCode(errorCode);
- }
- }
-
- return path;
- }
-
- ///
- /// Retrieves the current global memory status.
- ///
- internal static MemoryStatus GetMemoryStatus()
- {
- if (IsWindows)
- {
- MemoryStatus status = new MemoryStatus();
- bool returnValue = GlobalMemoryStatusEx(status);
- if (!returnValue)
- {
- return null;
- }
-
- return status;
- }
-
- return null;
- }
-
- ///
- /// Get the last write time of the fullpath to the file.
- ///
- /// Full path to the file in the filesystem
- /// The last write time of the file, or DateTime.MinValue if the file does not exist.
- ///
- /// This method should be accurate for regular files and symlinks, but can report incorrect data
- /// if the file's content was modified by writing to it through a different link, unless
- /// MSBUILDALWAYSCHECKCONTENTTIMESTAMP=1.
- ///
- internal static DateTime GetLastWriteFileUtcTime(string fullPath)
- {
-
- return LastWriteFileUtcTime(fullPath);
-
- DateTime LastWriteFileUtcTime(string path)
- {
- DateTime fileModifiedTime = DateTime.MinValue;
-
- if (IsWindows)
- {
- WIN32_FILE_ATTRIBUTE_DATA data = new WIN32_FILE_ATTRIBUTE_DATA();
- bool success = NativeMethods.GetFileAttributesEx(path, 0, ref data);
-
- if (success && (data.fileAttributes & NativeMethods.FILE_ATTRIBUTE_DIRECTORY) == 0)
- {
- long dt = ((long)(data.ftLastWriteTimeHigh) << 32) | ((long)data.ftLastWriteTimeLow);
- fileModifiedTime = DateTime.FromFileTimeUtc(dt);
-
- // If file is a symlink _and_ we're not instructed to do the wrong thing, get a more accurate timestamp.
- if ((data.fileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT)
- {
- fileModifiedTime = GetContentLastWriteFileUtcTime(path);
- }
- }
-
- return fileModifiedTime;
- }
- else
- {
- return File.Exists(path)
- ? File.GetLastWriteTimeUtc(path)
- : DateTime.MinValue;
- }
- }
- }
-
- ///
- /// Get the last write time of the content pointed to by a file path.
- ///
- /// Full path to the file in the filesystem
- /// The last write time of the file, or DateTime.MinValue if the file does not exist.
- ///
- /// This is the most accurate timestamp-extraction mechanism, but it is too slow to use all the time.
- /// See https://github.com/dotnet/msbuild/issues/2052.
- ///
- private static DateTime GetContentLastWriteFileUtcTime(string fullPath)
- {
- DateTime fileModifiedTime = DateTime.MinValue;
-
- using (SafeFileHandle handle =
- CreateFile(fullPath,
- GENERIC_READ,
- FILE_SHARE_READ,
- IntPtr.Zero,
- OPEN_EXISTING,
- FILE_ATTRIBUTE_NORMAL, /* No FILE_FLAG_OPEN_REPARSE_POINT; read through to content */
- IntPtr.Zero))
- {
- if (!handle.IsInvalid)
- {
- FILETIME ftCreationTime, ftLastAccessTime, ftLastWriteTime;
- if (GetFileTime(handle, out ftCreationTime, out ftLastAccessTime, out ftLastWriteTime))
- {
- long fileTime = ((long)(uint)ftLastWriteTime.dwHighDateTime) << 32 |
- (long)(uint)ftLastWriteTime.dwLowDateTime;
- fileModifiedTime =
- DateTime.FromFileTimeUtc(fileTime);
- }
- }
- }
-
- return fileModifiedTime;
- }
-
- ///
- /// Did the HRESULT succeed
- ///
- public static bool HResultSucceeded(int hr)
- {
- return hr >= 0;
- }
-
- ///
- /// Did the HRESULT Fail
- ///
- public static bool HResultFailed(int hr)
- {
- return hr < 0;
- }
-
- ///
- /// Given an error code, converts it to an HRESULT and throws the appropriate exception.
- ///
- ///
- public static void ThrowExceptionForErrorCode(int errorCode)
- {
- // See ndp\clr\src\bcl\system\io\__error.cs for this code as it appears in the CLR.
-
- // Something really bad went wrong with the call
- // translate the error into an exception
-
- // Convert the errorcode into an HRESULT (See MakeHRFromErrorCode in Win32Native.cs in
- // ndp\clr\src\bcl\microsoft\win32)
- errorCode = unchecked(((int)0x80070000) | errorCode);
-
- // Throw an exception as best we can
- Marshal.ThrowExceptionForHR(errorCode);
- }
-
- ///
- /// Kills the specified process by id and all of its children recursively.
- ///
- internal static void KillTree(int processIdToKill)
- {
- // Note that GetProcessById does *NOT* internally hold on to the process handle.
- // Only when you create the process using the Process object
- // does the Process object retain the original handle.
-
- Process thisProcess;
- try
- {
- thisProcess = Process.GetProcessById(processIdToKill);
- }
- catch (ArgumentException)
- {
- // The process has already died for some reason. So shrug and assume that any child processes
- // have all also either died or are in the process of doing so.
- return;
- }
-
- try
- {
- DateTime myStartTime = thisProcess.StartTime;
-
- // Grab the process handle. We want to keep this open for the duration of the function so that
- // it cannot be reused while we are running.
- SafeProcessHandle hProcess = OpenProcess(eDesiredAccess.PROCESS_QUERY_INFORMATION, false, processIdToKill);
- if (hProcess.IsInvalid)
- {
- return;
- }
-
- try
- {
- try
- {
- // Kill this process, so that no further children can be created.
- thisProcess.Kill();
- }
- catch (Win32Exception e)
- {
- // Access denied is potentially expected -- it happens when the process that
- // we're attempting to kill is already dead. So just ignore in that case.
- if (e.NativeErrorCode != ERROR_ACCESS_DENIED)
- {
- throw;
- }
- }
-
- // Now enumerate our children. Children of this process are any process which has this process id as its parent
- // and which also started after this process did.
- List> children = GetChildProcessIds(processIdToKill, myStartTime);
-
- try
- {
- foreach (KeyValuePair childProcessInfo in children)
- {
- KillTree(childProcessInfo.Key);
- }
- }
- finally
- {
- foreach (KeyValuePair childProcessInfo in children)
- {
- childProcessInfo.Value.Dispose();
- }
- }
- }
- finally
- {
- // Release the handle. After this point no more children of this process exist and this process has also exited.
- hProcess.Dispose();
- }
- }
- finally
- {
- thisProcess.Dispose();
- }
- }
-
- ///
- /// Returns the parent process id for the specified process.
- /// Returns zero if it cannot be gotten for some reason.
- ///
- internal static int GetParentProcessId(int processId)
- {
- int ParentID = 0;
-#if !CLR2COMPATIBILITY
- if (IsUnixLike)
- {
- string line = null;
-
- try
- {
- // /proc//stat returns a bunch of space separated fields. Get that string
-
- // TODO: this was
- // using (var r = FileUtilities.OpenRead("/proc/" + processId + "/stat"))
- // and could be again when FileUtilities moves to Framework
-
- using var fileStream = new FileStream("/proc/" + processId + "/stat", FileMode.Open, FileAccess.Read);
- using StreamReader r = new(fileStream);
-
- line = r.ReadLine();
- }
- catch // Ignore errors since the process may have terminated
- {
- }
-
- if (!string.IsNullOrWhiteSpace(line))
- {
- // One of the fields is the process name. It may contain any characters, but since it's
- // in parenthesis, we can finds its end by looking for the last parenthesis. After that,
- // there comes a space, then the second fields separated by a space is the parent id.
- string[] statFields = line.Substring(line.LastIndexOf(')')).Split(MSBuildConstants.SpaceChar, 4);
- if (statFields.Length >= 3)
- {
- ParentID = Int32.Parse(statFields[2]);
- }
- }
- }
- else
-#endif
- {
- SafeProcessHandle hProcess = OpenProcess(eDesiredAccess.PROCESS_QUERY_INFORMATION, false, processId);
-
- if (!hProcess.IsInvalid)
- {
- try
- {
- // UNDONE: NtQueryInformationProcess will fail if we are not elevated and other process is. Advice is to change to use ToolHelp32 API's
- // For now just return zero and worst case we will not kill some children.
- PROCESS_BASIC_INFORMATION pbi = new PROCESS_BASIC_INFORMATION();
- int pSize = 0;
-
- if (0 == NtQueryInformationProcess(hProcess, PROCESSINFOCLASS.ProcessBasicInformation, ref pbi, pbi.Size, ref pSize))
- {
- ParentID = (int)pbi.InheritedFromUniqueProcessId;
- }
- }
- finally
- {
- hProcess.Dispose();
- }
- }
- }
-
- return ParentID;
- }
-
- ///
- /// Returns an array of all the immediate child processes by id.
- /// NOTE: The IntPtr in the tuple is the handle of the child process. CloseHandle MUST be called on this.
- ///
- internal static List> GetChildProcessIds(int parentProcessId, DateTime parentStartTime)
- {
- List> myChildren = new List>();
-
- foreach (Process possibleChildProcess in Process.GetProcesses())
- {
- using (possibleChildProcess)
- {
- // Hold the child process handle open so that children cannot die and restart with a different parent after we've started looking at it.
- // This way, any handle we pass back is guaranteed to be one of our actual children.
- SafeProcessHandle childHandle = OpenProcess(eDesiredAccess.PROCESS_QUERY_INFORMATION, false, possibleChildProcess.Id);
- if (childHandle.IsInvalid)
- {
- continue;
- }
-
- bool keepHandle = false;
- try
- {
- if (possibleChildProcess.StartTime > parentStartTime)
- {
- int childParentProcessId = GetParentProcessId(possibleChildProcess.Id);
- if (childParentProcessId != 0)
- {
- if (parentProcessId == childParentProcessId)
- {
- // Add this one
- myChildren.Add(new KeyValuePair(possibleChildProcess.Id, childHandle));
- keepHandle = true;
- }
- }
- }
- }
- finally
- {
- if (!keepHandle)
- {
- childHandle.Dispose();
- }
- }
- }
- }
-
- return myChildren;
- }
-
- ///
- /// Internal, optimized GetCurrentDirectory implementation that simply delegates to the native method
- ///
- ///
- internal unsafe static string GetCurrentDirectory()
- {
-#if FEATURE_LEGACY_GETCURRENTDIRECTORY
- if (IsWindows)
- {
- int bufferSize = GetCurrentDirectoryWin32(0, null);
- char* buffer = stackalloc char[bufferSize];
- int pathLength = GetCurrentDirectoryWin32(bufferSize, buffer);
- return new string(buffer, startIndex: 0, length: pathLength);
- }
-#endif
- return Directory.GetCurrentDirectory();
- }
-
- private unsafe static int GetCurrentDirectoryWin32(int nBufferLength, char* lpBuffer)
- {
- int pathLength = GetCurrentDirectory(nBufferLength, lpBuffer);
- VerifyThrowWin32Result(pathLength);
- return pathLength;
- }
-
- internal unsafe static string GetFullPath(string path)
- {
- int bufferSize = GetFullPathWin32(path, 0, null, IntPtr.Zero);
- char* buffer = stackalloc char[bufferSize];
- int fullPathLength = GetFullPathWin32(path, bufferSize, buffer, IntPtr.Zero);
- // Avoid creating new strings unnecessarily
- return AreStringsEqual(buffer, fullPathLength, path) ? path : new string(buffer, startIndex: 0, length: fullPathLength);
- }
-
- private unsafe static int GetFullPathWin32(string target, int bufferLength, char* buffer, IntPtr mustBeZero)
- {
- int pathLength = GetFullPathName(target, bufferLength, buffer, mustBeZero);
- VerifyThrowWin32Result(pathLength);
- return pathLength;
- }
-
- ///
- /// Compare an unsafe char buffer with a to see if their contents are identical.
- ///
- /// The beginning of the char buffer.
- /// The length of the buffer.
- /// The string.
- /// True only if the contents of and the first characters in are identical.
- private unsafe static bool AreStringsEqual(char* buffer, int len, string s)
- {
- if (len != s.Length)
- {
- return false;
- }
-
- foreach (char ch in s)
- {
- if (ch != *buffer++)
- {
- return false;
- }
- }
-
- return true;
- }
-
- internal static void VerifyThrowWin32Result(int result)
- {
- bool isError = result == 0;
- if (isError)
- {
- int code = Marshal.GetLastWin32Error();
- ThrowExceptionForErrorCode(code);
- }
- }
-
- #endregion
-
- #region PInvoke
-
- ///
- /// Gets the current OEM code page which is used by console apps
- /// (as opposed to the Windows/ANSI code page)
- /// Basically for each ANSI code page (set in Regional settings) there's a corresponding OEM code page
- /// that needs to be used for instance when writing to batch files
- ///
- [DllImport(kernel32Dll)]
- internal static extern int GetOEMCP();
-
- [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
- [return: MarshalAs(UnmanagedType.Bool)]
- internal static extern bool GetFileAttributesEx(String name, int fileInfoLevel, ref WIN32_FILE_ATTRIBUTE_DATA lpFileInformation);
-
- [DllImport(kernel32Dll, SetLastError = true, CharSet = CharSet.Unicode)]
- private static extern uint SearchPath
- (
- string path,
- string fileName,
- string extension,
- int numBufferChars,
- [Out] StringBuilder buffer,
- int[] filePart
- );
-
- [DllImport("kernel32.dll", PreserveSig = true, SetLastError = true)]
- [return: MarshalAs(UnmanagedType.Bool)]
- internal static extern bool FreeLibrary([In] IntPtr module);
-
- [DllImport("kernel32.dll", PreserveSig = true, BestFitMapping = false, ThrowOnUnmappableChar = true, CharSet = CharSet.Ansi, SetLastError = true)]
- internal static extern IntPtr GetProcAddress(IntPtr module, string procName);
-
- [DllImport("kernel32.dll", CharSet = CharSet.Unicode, PreserveSig = true, SetLastError = true)]
- internal static extern IntPtr LoadLibrary(string fileName);
-
- [DllImport(mscoreeDLL, SetLastError = true, CharSet = CharSet.Unicode)]
- internal static extern uint GetRequestedRuntimeInfo(String pExe,
- String pwszVersion,
- String pConfigurationFile,
- uint startupFlags,
- uint runtimeInfoFlags,
- [Out] StringBuilder pDirectory,
- int dwDirectory,
- out uint dwDirectoryLength,
- [Out] StringBuilder pVersion,
- int cchBuffer,
- out uint dwlength);
-
- ///
- /// Gets the fully qualified filename of the currently executing .exe
- ///
- [DllImport(kernel32Dll, SetLastError = true, CharSet = CharSet.Unicode)]
- internal static extern int GetModuleFileName(
-#if FEATURE_HANDLEREF
- HandleRef hModule,
-#else
- IntPtr hModule,
-#endif
- [Out] StringBuilder buffer, int length);
-
- [DllImport("kernel32.dll")]
- internal static extern IntPtr GetStdHandle(int nStdHandle);
-
- [DllImport("kernel32.dll")]
- internal static extern uint GetFileType(IntPtr hFile);
-
- [SuppressMessage("Microsoft.Usage", "CA2205:UseManagedEquivalentsOfWin32Api", Justification = "Using unmanaged equivalent for performance reasons")]
- [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
- internal unsafe static extern int GetCurrentDirectory(int nBufferLength, char* lpBuffer);
-
- [SuppressMessage("Microsoft.Usage", "CA2205:UseManagedEquivalentsOfWin32Api", Justification = "Using unmanaged equivalent for performance reasons")]
- [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "SetCurrentDirectory")]
- [return: MarshalAs(UnmanagedType.Bool)]
- internal static extern bool SetCurrentDirectoryWindows(string path);
-
- internal static bool SetCurrentDirectory(string path)
- {
- if (IsWindows)
- {
- return SetCurrentDirectoryWindows(path);
- }
-
- // Make sure this does not throw
- try
- {
- Directory.SetCurrentDirectory(path);
- }
- catch
- {
- }
- return true;
- }
-
- [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
- internal static unsafe extern int GetFullPathName(string target, int bufferLength, char* buffer, IntPtr mustBeZero);
-
- [DllImport("KERNEL32.DLL")]
- private static extern SafeProcessHandle OpenProcess(eDesiredAccess dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, int dwProcessId);
-
- [DllImport("NTDLL.DLL")]
- private static extern int NtQueryInformationProcess(SafeProcessHandle hProcess, PROCESSINFOCLASS pic, ref PROCESS_BASIC_INFORMATION pbi, uint cb, ref int pSize);
-
- [return: MarshalAs(UnmanagedType.Bool)]
- [DllImport("kernel32.dll", CharSet = AutoOrUnicode, SetLastError = true)]
- private static extern bool GlobalMemoryStatusEx([In, Out] MemoryStatus lpBuffer);
-
- [DllImport("kernel32.dll", CharSet = CharSet.Unicode, BestFitMapping = false)]
- internal static extern int GetShortPathName(string path, [Out] StringBuilder fullpath, [In] int length);
-
- [DllImport("kernel32.dll", CharSet = CharSet.Unicode, BestFitMapping = false)]
- internal static extern int GetLongPathName([In] string path, [Out] StringBuilder fullpath, [In] int length);
-
- [DllImport("kernel32.dll", CharSet = AutoOrUnicode, SetLastError = true)]
- internal static extern bool CreatePipe(out SafeFileHandle hReadPipe, out SafeFileHandle hWritePipe, SecurityAttributes lpPipeAttributes, int nSize);
-
- [DllImport("kernel32.dll", CharSet = AutoOrUnicode, SetLastError = true)]
- internal static extern bool ReadFile(SafeFileHandle hFile, byte[] lpBuffer, uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, IntPtr lpOverlapped);
-
- ///
- /// CoWaitForMultipleHandles allows us to wait in an STA apartment and still service RPC requests from other threads.
- /// VS needs this in order to allow the in-proc compilers to properly initialize, since they will make calls from the
- /// build thread which the main thread (blocked on BuildSubmission.Execute) must service.
- ///
- [DllImport("ole32.dll")]
- public static extern int CoWaitForMultipleHandles(COWAIT_FLAGS dwFlags, int dwTimeout, int cHandles, [MarshalAs(UnmanagedType.LPArray)] IntPtr[] pHandles, out int pdwIndex);
-
- internal const uint GENERIC_READ = 0x80000000;
- internal const uint FILE_SHARE_READ = 0x1;
- internal const uint FILE_ATTRIBUTE_NORMAL = 0x80;
- internal const uint FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000;
- internal const uint OPEN_EXISTING = 3;
-
- [DllImport("kernel32.dll", CharSet = AutoOrUnicode, CallingConvention = CallingConvention.StdCall,
- SetLastError = true)]
- internal static extern SafeFileHandle CreateFile(
- string lpFileName,
- uint dwDesiredAccess,
- uint dwShareMode,
- IntPtr lpSecurityAttributes,
- uint dwCreationDisposition,
- uint dwFlagsAndAttributes,
- IntPtr hTemplateFile
- );
-
- [DllImport("kernel32.dll", SetLastError = true)]
- internal static extern bool GetFileTime(
- SafeFileHandle hFile,
- out FILETIME lpCreationTime,
- out FILETIME lpLastAccessTime,
- out FILETIME lpLastWriteTime
- );
-
- [DllImport("kernel32.dll", SetLastError = true)]
- [return: MarshalAs(UnmanagedType.Bool)]
- internal static extern bool CloseHandle(IntPtr hObject);
-
- [DllImport("kernel32.dll", SetLastError = true)]
- internal static extern bool SetThreadErrorMode(int newMode, out int oldMode);
-
- #endregion
-
- #region Extensions
-
- ///
- /// Waits while pumping APC messages. This is important if the waiting thread is an STA thread which is potentially
- /// servicing COM calls from other threads.
- ///
- [SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Runtime.InteropServices.SafeHandle.DangerousGetHandle", Scope = "member", Target = "Microsoft.Build.Shared.NativeMethodsShared.#MsgWaitOne(System.Threading.WaitHandle,System.Int32)", Justification = "This is necessary and it has been used for a long time. No need to change it now.")]
- internal static bool MsgWaitOne(this WaitHandle handle)
- {
- return handle.MsgWaitOne(Timeout.Infinite);
- }
-
- ///
- /// Waits while pumping APC messages. This is important if the waiting thread is an STA thread which is potentially
- /// servicing COM calls from other threads.
- ///
- internal static bool MsgWaitOne(this WaitHandle handle, TimeSpan timeout)
- {
- return MsgWaitOne(handle, (int)timeout.TotalMilliseconds);
- }
-
- ///
- /// Waits while pumping APC messages. This is important if the waiting thread is an STA thread which is potentially
- /// servicing COM calls from other threads.
- ///
- [SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Runtime.InteropServices.SafeHandle.DangerousGetHandle", Justification = "Necessary to avoid pumping")]
- internal static bool MsgWaitOne(this WaitHandle handle, int timeout)
- {
- // CoWaitForMultipleHandles allows us to wait in an STA apartment and still service RPC requests from other threads.
- // VS needs this in order to allow the in-proc compilers to properly initialize, since they will make calls from the
- // build thread which the main thread (blocked on BuildSubmission.Execute) must service.
- int waitIndex;
-#if FEATURE_HANDLE_SAFEWAITHANDLE
- IntPtr handlePtr = handle.SafeWaitHandle.DangerousGetHandle();
-#else
- IntPtr handlePtr = handle.GetSafeWaitHandle().DangerousGetHandle();
-#endif
- int returnValue = CoWaitForMultipleHandles(COWAIT_FLAGS.COWAIT_NONE, timeout, 1, new IntPtr[] { handlePtr }, out waitIndex);
-
- if (!(returnValue == 0 || ((uint)returnValue == RPC_S_CALLPENDING && timeout != Timeout.Infinite)))
- {
- throw new InternalErrorException($"Received {returnValue} from CoWaitForMultipleHandles, but expected 0 (S_OK)");
- }
-
- return returnValue == 0;
- }
-
- #endregion
-
- #region helper methods
-
- internal static bool DirectoryExists(string fullPath)
- {
- return IsWindows
- ? DirectoryExistsWindows(fullPath)
- : Directory.Exists(fullPath);
- }
-
- internal static bool DirectoryExistsWindows(string fullPath)
- {
- WIN32_FILE_ATTRIBUTE_DATA data = new WIN32_FILE_ATTRIBUTE_DATA();
- bool success = GetFileAttributesEx(fullPath, 0, ref data);
- return success && (data.fileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
- }
-
- internal static bool FileExists(string fullPath)
- {
- return IsWindows
- ? FileExistsWindows(fullPath)
- : File.Exists(fullPath);
- }
-
- internal static bool FileExistsWindows(string fullPath)
- {
- WIN32_FILE_ATTRIBUTE_DATA data = new WIN32_FILE_ATTRIBUTE_DATA();
- bool success = GetFileAttributesEx(fullPath, 0, ref data);
- return success && (data.fileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0;
- }
-
- internal static bool FileOrDirectoryExists(string path)
- {
- return IsWindows
- ? FileOrDirectoryExistsWindows(path)
- : File.Exists(path) || Directory.Exists(path);
- }
-
- internal static bool FileOrDirectoryExistsWindows(string path)
- {
- WIN32_FILE_ATTRIBUTE_DATA data = new WIN32_FILE_ATTRIBUTE_DATA();
- return GetFileAttributesEx(path, 0, ref data);
- }
-
- #endregion
-
- }
-}
diff --git a/src/Ionide.ProjInfo.Sln/ProbablyUnusedNamespaces.cs b/src/Ionide.ProjInfo.Sln/ProbablyUnusedNamespaces.cs
deleted file mode 100644
index facbda86..00000000
--- a/src/Ionide.ProjInfo.Sln/ProbablyUnusedNamespaces.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-
-namespace Ionide.ProjInfo.Sln.Shared.XMakeAttributes { }
-
-namespace Ionide.ProjInfo.Sln.Utilities {}
-
-namespace Ionide.ProjInfo.Sln.Evaluation {}
diff --git a/src/Ionide.ProjInfo.Sln/README.md b/src/Ionide.ProjInfo.Sln/README.md
deleted file mode 100644
index dac26b64..00000000
--- a/src/Ionide.ProjInfo.Sln/README.md
+++ /dev/null
@@ -1,27 +0,0 @@
-### Ported from: https://github.com/enricosada/sln
-
-WHY? no official sln parser avaiable
-
-https://github.com/Microsoft/msbuild/issues/1708#issuecomment-280693611
-
-Reuse some source files from https://github.com/Microsoft/msbuild/ repo
-and reimplement some classes to not import too many files
-
-Original file list:
-
-```xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
\ No newline at end of file
diff --git a/src/Ionide.ProjInfo.Sln/Reimplemented.cs b/src/Ionide.ProjInfo.Sln/Reimplemented.cs
deleted file mode 100644
index b1e87dd9..00000000
--- a/src/Ionide.ProjInfo.Sln/Reimplemented.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Ionide.ProjInfo.Sln.Shared
-{
- internal interface IElementLocation
- {
- string File { get; }
- int Line { get; }
- int Column { get; }
- string LocationString { get; }
- }
-
- internal class InternalErrorException : Exception
- {
- public InternalErrorException(string m) : base(m) { }
- public InternalErrorException(string m, Exception iex) : base(m, iex) { }
- }
-}
diff --git a/src/Ionide.ProjInfo.Sln/ResourcesRelated.cs b/src/Ionide.ProjInfo.Sln/ResourcesRelated.cs
deleted file mode 100644
index dae07ae1..00000000
--- a/src/Ionide.ProjInfo.Sln/ResourcesRelated.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Ionide.ProjInfo.Sln.Shared
-{
- internal static class ResourceUtilities
- {
- public static string FormatResourceString(out string e, out string x, string s, params object[] args)
- {
- e = x = "";
- return string.Format(s, args);
- }
-
- public static string FormatResourceString(string s, params object[] args)
- {
- return string.Format(s, args);
- }
-
- public static string FormatString(string s, params object[] args)
- {
- return string.Format(s, args);
- }
-
- internal static void VerifyResourceStringExists(string resourceName)
- {
- }
- }
-
- internal static class AssemblyResources
- {
- internal static string GetString(string name)
- {
- return name;
- }
- }
-}
diff --git a/src/Ionide.ProjInfo.Sln/vendor/BuildEventFileInfo.cs b/src/Ionide.ProjInfo.Sln/vendor/BuildEventFileInfo.cs
deleted file mode 100644
index 0726450b..00000000
--- a/src/Ionide.ProjInfo.Sln/vendor/BuildEventFileInfo.cs
+++ /dev/null
@@ -1,190 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-using System;
-using System.Xml;
-using System.Xml.Schema;
-
-namespace Ionide.ProjInfo.Sln.Shared
-{
- ///
- /// This class encapsulates information about a file that is associated with a build event.
- ///
- internal sealed class BuildEventFileInfo
- {
- #region Constructors
-
- ///
- /// Private default constructor disallows parameterless instantiation.
- ///
- private BuildEventFileInfo()
- {
- // do nothing
- }
-
- ///
- /// Creates an instance of this class using the given filename/path.
- /// Filename may be an empty string, if there is truly no file associated.
- /// This overload may also be used if there is a file but truly no line/column,
- /// for example when failing to load a project file.
- ///
- /// IF AN IELEMENTLOCATION IS AVAILABLE, USE THE OVERLOAD ACCEPTING THAT INSTEAD.
- ///
- ///
- internal BuildEventFileInfo(string file)
- : this(file, 0, 0, 0, 0)
- {
- // do nothing
- }
-
- ///
- /// Creates an instance of this class using the given location.
- /// This does not provide end-line or end-column information.
- /// This is the preferred overload.
- ///
- internal BuildEventFileInfo(IElementLocation location)
- : this(location.File, location.Line, location.Column)
- {
- // do nothing
- }
-
- ///
- /// Creates an instance of this class using the given filename/path and a line/column of interest in the file.
- ///
- /// IF AN IELEMENTLOCATION IS AVAILABLE, USE THE OVERLOAD ACCEPTING THAT INSTEAD.
- ///
- ///
- /// Set to zero if not available.
- /// Set to zero if not available.
- internal BuildEventFileInfo(string file, int line, int column)
- : this(file, line, column, 0, 0)
- {
- // do nothing
- }
-
- ///
- /// Creates an instance of this class using the given filename/path and a range of lines/columns of interest in the file.
- ///
- /// IF AN IELEMENTLOCATION IS AVAILABLE, USE THE OVERLOAD ACCEPTING THAT INSTEAD.
- ///
- ///
- /// Set to zero if not available.
- /// Set to zero if not available.
- /// Set to zero if not available.
- /// Set to zero if not available.
- internal BuildEventFileInfo(string file, int line, int column, int endLine, int endColumn)
- {
- // Projects that don't have a filename when the are built should use an empty string instead.
- _file = (file == null) ? String.Empty : file;
- _line = line;
- _column = column;
- _endLine = endLine;
- _endColumn = endColumn;
- }
-
- ///
- /// Creates an instance of this class using the information in the given XmlException.
- ///
- ///
- internal BuildEventFileInfo(XmlException e)
- {
- ErrorUtilities.VerifyThrow(e != null, "Need exception context.");
-#if FEATURE_XML_SOURCE_URI
- _file = (e.SourceUri.Length == 0) ? String.Empty : new Uri(e.SourceUri).LocalPath;
-#else
- _file = String.Empty;
-#endif
- _line = e.LineNumber;
- _column = e.LinePosition;
- _endLine = 0;
- _endColumn = 0;
- }
-
- ///
- /// Creates an instance of this class using the information in the given XmlException and file location.
- ///
- internal BuildEventFileInfo(string file, XmlException e) : this(e)
- {
- ErrorUtilities.VerifyThrowArgumentNull(file, nameof(file));
-
- _file = file;
- }
-
- #endregion
-
- #region Properties
-
- ///
- /// Gets the filename/path to be associated with some build event.
- ///
- /// The filename/path string.
- internal string File
- {
- get
- {
- return _file;
- }
- }
-
- ///
- /// Gets the line number of interest in the file.
- ///
- /// Line number, or zero if not available.
- internal int Line
- {
- get
- {
- return _line;
- }
- }
-
- ///
- /// Gets the column number of interest in the file.
- ///
- /// Column number, or zero if not available.
- internal int Column
- {
- get
- {
- return _column;
- }
- }
-
- ///
- /// Gets the last line number of a range of interesting lines in the file.
- ///
- /// Last line number, or zero if not available.
- internal int EndLine
- {
- get
- {
- return _endLine;
- }
- }
-
- ///
- /// Gets the last column number of a range of interesting columns in the file.
- ///
- /// Last column number, or zero if not available.
- internal int EndColumn
- {
- get
- {
- return _endColumn;
- }
- }
-
- #endregion
-
- // the filename/path
- private string _file;
- // the line number of interest in the file
- private int _line;
- // the column number of interest in the file
- private int _column;
- // the last line in a range of interesting lines in the file
- private int _endLine;
- // the last column in a range of interesting columns in the file
- private int _endColumn;
- }
-}
diff --git a/src/Ionide.ProjInfo.Sln/vendor/Constants.cs b/src/Ionide.ProjInfo.Sln/vendor/Constants.cs
deleted file mode 100644
index c66b487d..00000000
--- a/src/Ionide.ProjInfo.Sln/vendor/Constants.cs
+++ /dev/null
@@ -1,147 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-using System;
-using System.Reflection;
-
-// This file is compiled into both Microsoft.Build.Framework and Microsoft.Build.Tasks which can cause collisions.
-#if MICROSOFT_BUILD_TASKS
-namespace Microsoft.Build.Tasks
-#else
-namespace Ionide.ProjInfo.Sln.Shared
-#endif
-{
- ///
- /// Constants that we want to be shareable across all our assemblies.
- ///
- internal static class MSBuildConstants
- {
- ///
- /// The name of the property that indicates the tools path
- ///
- internal const string ToolsPath = "MSBuildToolsPath";
-
- ///
- /// Name of the property that indicates the X64 tools path
- ///
- internal const string ToolsPath64 = "MSBuildToolsPath64";
-
- ///
- /// Name of the property that indicates the root of the SDKs folder
- ///
- internal const string SdksPath = "MSBuildSDKsPath";
-
- ///
- /// Name of the property that indicates that all warnings should be treated as errors.
- ///
- internal const string TreatWarningsAsErrors = "MSBuildTreatWarningsAsErrors";
-
- ///
- /// Name of the property that indicates a list of warnings to treat as errors.
- ///
- internal const string WarningsAsErrors = "MSBuildWarningsAsErrors";
-
- ///
- /// Name of the property that indicates the list of warnings to treat as messages.
- ///
- internal const string WarningsAsMessages = "MSBuildWarningsAsMessages";
-
- ///
- /// The most current Visual Studio Version known to this version of MSBuild.
- ///
-#if STANDALONEBUILD
- internal const string CurrentVisualStudioVersion = "15.0";
-#else
- internal const string CurrentVisualStudioVersion = Microsoft.VisualStudio.Internal.BrandNames.VSGeneralVersion;
-#endif
-
- ///
- /// The most current ToolsVersion known to this version of MSBuild.
- ///
- internal const string CurrentToolsVersion = CurrentVisualStudioVersion;
-
- // if you change the key also change the following clones
- // Microsoft.Build.OpportunisticIntern.BucketedPrioritizedStringList.TryIntern
- internal const string MSBuildDummyGlobalPropertyHeader = "MSBuildProjectInstance";
-
- ///
- /// The most current ToolsVersion known to this version of MSBuild as a Version object.
- ///
- internal static Version CurrentToolsVersionAsVersion = new Version(CurrentToolsVersion);
-
- ///
- /// The most current VSGeneralAssemblyVersion known to this version of MSBuild.
- ///
-#if STANDALONEBUILD
- internal const string CurrentAssemblyVersion = "15.1.0.0";
-#else
- internal const string CurrentAssemblyVersion = Microsoft.VisualStudio.Internal.BrandNames.VSGeneralAssemblyVersion;
-#endif
-
- internal const string CurrentAssemblyFileVersion = "15.5.0.0";
-
- ///
- /// Current version of this MSBuild Engine assembly in the form, e.g, "12.0"
- ///
- internal static string CurrentProductVersion
- {
- get
- {
-#if STANDALONEBUILD
- return "15.0";
-#else
- Version thisAssemblyVersion = new Version(ThisAssembly.Version);
- // "12.0.0.0" --> "12.0"
- return thisAssemblyVersion.Major + "." + thisAssemblyVersion.Minor;
-#endif
- }
- }
-
- internal static readonly char[] DirectorySeparatorChar = { System.IO.Path.DirectorySeparatorChar };
-
- internal static readonly char[] SpaceChar = { ' ' };
- }
-
- ///
- /// Constants naming well-known item metadata.
- ///
- internal static class ItemMetadataNames
- {
- internal const string fusionName = "FusionName";
- internal const string hintPath = "HintPath";
- internal const string assemblyFolderKey = "AssemblyFolderKey";
- internal const string alias = "Alias";
- internal const string aliases = "Aliases";
- internal const string parentFile = "ParentFile";
- internal const string privateMetadata = "Private";
- internal const string copyLocal = "CopyLocal";
- internal const string isRedistRoot = "IsRedistRoot";
- internal const string redist = "Redist";
- internal const string resolvedFrom = "ResolvedFrom";
- internal const string destinationSubDirectory = "DestinationSubDirectory";
- internal const string specificVersion = "SpecificVersion";
- internal const string link = "Link";
- internal const string subType = "SubType";
- internal const string executableExtension = "ExecutableExtension";
- internal const string embedInteropTypes = "EmbedInteropTypes";
- internal const string targetPath = "TargetPath";
- internal const string dependentUpon = "DependentUpon";
- internal const string msbuildSourceProjectFile = "MSBuildSourceProjectFile";
- internal const string msbuildSourceTargetName = "MSBuildSourceTargetName";
- internal const string isPrimary = "IsPrimary";
- internal const string targetFramework = "RequiredTargetFramework";
- internal const string frameworkDirectory = "FrameworkDirectory";
- internal const string version = "Version";
- internal const string imageRuntime = "ImageRuntime";
- internal const string winMDFile = "WinMDFile";
- internal const string winMDFileType = "WinMDFileType";
- internal const string msbuildReferenceSourceTarget = "ReferenceSourceTarget";
- internal const string msbuildReferenceGrouping = "ReferenceGrouping";
- internal const string msbuildReferenceGroupingDisplayName = "ReferenceGroupingDisplayName";
- internal const string msbuildReferenceFromSDK = "ReferenceFromSDK";
- internal const string winmdImplmentationFile = "Implementation";
- internal const string projectReferenceOriginalItemSpec = "ProjectReferenceOriginalItemSpec";
- internal const string IgnoreVersionForFrameworkReference = "IgnoreVersionForFrameworkReference";
- internal const string frameworkFile = "FrameworkFile";
- }
-}
diff --git a/src/Ionide.ProjInfo.Sln/vendor/ErrorUtilities.cs b/src/Ionide.ProjInfo.Sln/vendor/ErrorUtilities.cs
deleted file mode 100644
index 58285c4c..00000000
--- a/src/Ionide.ProjInfo.Sln/vendor/ErrorUtilities.cs
+++ /dev/null
@@ -1,801 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-using System;
-using System.IO;
-using System.Diagnostics;
-using System.Globalization;
-using System.Runtime.CompilerServices;
-using System.Threading;
-
-#if BUILDINGAPPXTASKS
-namespace Microsoft.Build.AppxPackage.Shared
-#else
-namespace Ionide.ProjInfo.Sln.Shared
-#endif
-{
- ///
- /// This class contains methods that are useful for error checking and validation.
- ///
- internal static class ErrorUtilities
- {
- ///
- /// Emergency escape hatch. If a customer hits a bug in the shipped product causing an internal exception,
- /// and fortuitously it happens that ignoring the VerifyThrow allows execution to continue in a reasonable way,
- /// then we can give them this undocumented environment variable as an immediate workaround.
- ///
- private static readonly bool s_throwExceptions = String.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDDONOTTHROWINTERNAL"));
- private static readonly bool s_enableMSBuildDebugTracing = !String.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDENABLEDEBUGTRACING"));
-
- #region DebugTracing
- public static void DebugTraceMessage(string category, string formatstring, params object[] parameters)
- {
- if (s_enableMSBuildDebugTracing)
- {
- if (parameters != null)
- {
- Trace.WriteLine(String.Format(CultureInfo.CurrentCulture, formatstring, parameters), category);
- }
- else
- {
- Trace.WriteLine(formatstring, category);
- }
- }
- }
- #endregion
-
-#if !BUILDINGAPPXTASKS
- #region VerifyThrow -- for internal errors
-
- ///
- /// Throws InternalErrorException.
- /// This is only for situations that would mean that there is a bug in MSBuild itself.
- ///
- internal static void ThrowInternalError(string message, params object[] args)
- {
- if (s_throwExceptions)
- {
- throw new InternalErrorException(ResourceUtilities.FormatString(message, args));
- }
- }
-
- ///
- /// Throws InternalErrorException.
- /// This is only for situations that would mean that there is a bug in MSBuild itself.
- ///
- internal static void ThrowInternalError(string message, Exception innerException, params object[] args)
- {
- if (s_throwExceptions)
- {
- throw new InternalErrorException(ResourceUtilities.FormatString(message, args), innerException);
- }
- }
-
- ///
- /// Throws InternalErrorException.
- /// Indicates the code path followed should not have been possible.
- /// This is only for situations that would mean that there is a bug in MSBuild itself.
- ///
- internal static void ThrowInternalErrorUnreachable()
- {
- if (s_throwExceptions)
- {
- throw new InternalErrorException("Unreachable?");
- }
- }
-
- ///
- /// Throws InternalErrorException.
- /// Indicates the code path followed should not have been possible.
- /// This is only for situations that would mean that there is a bug in MSBuild itself.
- ///
- internal static void ThrowIfTypeDoesNotImplementToString(object param)
- {
-#if DEBUG
- // Check it has a real implementation of ToString()
- if (String.Equals(param.GetType().ToString(), param.ToString(), StringComparison.Ordinal))
- {
- ErrorUtilities.ThrowInternalError("This type does not implement ToString() properly {0}", param.GetType().FullName);
- }
-#endif
- }
-
- ///
- /// Helper to throw an InternalErrorException when the specified parameter is null.
- /// This should be used ONLY if this would indicate a bug in MSBuild rather than
- /// anything caused by user action.
- ///
- /// The value of the argument.
- /// Parameter that should not be null
- internal static void VerifyThrowInternalNull(object parameter, string parameterName)
- {
- if (parameter == null)
- {
- ThrowInternalError("{0} unexpectedly null", parameterName);
- }
- }
-
- ///
- /// Helper to throw an InternalErrorException when a lock on the specified object is not already held.
- /// This should be used ONLY if this would indicate a bug in MSBuild rather than
- /// anything caused by user action.
- ///
- /// The object that should already have been used as a lock.
- internal static void VerifyThrowInternalLockHeld(object locker)
- {
-#if !CLR2COMPATIBILITY
- if (!Monitor.IsEntered(locker))
- {
- ThrowInternalError("Lock should already have been taken");
- }
-#endif
- }
-
- ///
- /// Helper to throw an InternalErrorException when the specified parameter is null or zero length.
- /// This should be used ONLY if this would indicate a bug in MSBuild rather than
- /// anything caused by user action.
- ///
- /// The value of the argument.
- /// Parameter that should not be null or zero length
- internal static void VerifyThrowInternalLength(string parameterValue, string parameterName)
- {
- VerifyThrowInternalNull(parameterValue, parameterName);
-
- if (parameterValue.Length == 0)
- {
- ThrowInternalError("{0} unexpectedly empty", parameterName);
- }
- }
-
- ///
- /// Helper to throw an InternalErrorException when the specified parameter is not a rooted path.
- /// This should be used ONLY if this would indicate a bug in MSBuild rather than
- /// anything caused by user action.
- ///
- /// Parameter that should be a rooted path
- internal static void VerifyThrowInternalRooted(string value)
- {
- if (!Path.IsPathRooted(value))
- {
- ThrowInternalError("{0} unexpectedly not a rooted path", value);
- }
- }
-
- ///
- /// This method should be used in places where one would normally put
- /// an "assert". It should be used to validate that our assumptions are
- /// true, where false would indicate that there must be a bug in our
- /// code somewhere. This should not be used to throw errors based on bad
- /// user input or anything that the user did wrong.
- ///
- ///
- ///
- internal static void VerifyThrow
- (
- bool condition,
- string unformattedMessage
- )
- {
- if (!condition)
- {
- // PERF NOTE: explicitly passing null for the arguments array
- // prevents memory allocation
- ThrowInternalError(unformattedMessage, null, null);
- }
- }
-
- ///
- /// Overload for one string format argument.
- ///
- ///
- ///
- ///
- internal static void VerifyThrow
- (
- bool condition,
- string unformattedMessage,
- object arg0
- )
- {
- // PERF NOTE: check the condition here instead of pushing it into
- // the ThrowInternalError() method, because that method always
- // allocates memory for its variable array of arguments
- if (!condition)
- {
- ThrowInternalError(unformattedMessage, arg0);
- }
- }
-
- ///
- /// Overload for two string format arguments.
- ///
- ///
- ///
- ///
- ///
- internal static void VerifyThrow
- (
- bool condition,
- string unformattedMessage,
- object arg0,
- object arg1
- )
- {
- // PERF NOTE: check the condition here instead of pushing it into
- // the ThrowInternalError() method, because that method always
- // allocates memory for its variable array of arguments
- if (!condition)
- {
- ThrowInternalError(unformattedMessage, arg0, arg1);
- }
- }
-
- ///
- /// Overload for three string format arguments.
- ///
- ///
- ///
- ///
- ///
- ///
- internal static void VerifyThrow
- (
- bool condition,
- string unformattedMessage,
- object arg0,
- object arg1,
- object arg2
- )
- {
- // PERF NOTE: check the condition here instead of pushing it into
- // the ThrowInternalError() method, because that method always
- // allocates memory for its variable array of arguments
- if (!condition)
- {
- ThrowInternalError(unformattedMessage, arg0, arg1, arg2);
- }
- }
-
- ///
- /// Overload for four string format arguments.
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- internal static void VerifyThrow
- (
- bool condition,
- string unformattedMessage,
- object arg0,
- object arg1,
- object arg2,
- object arg3
- )
- {
- // PERF NOTE: check the condition here instead of pushing it into
- // the ThrowInternalError() method, because that method always
- // allocates memory for its variable array of arguments
- if (!condition)
- {
- ThrowInternalError(unformattedMessage, arg0, arg1, arg2, arg3);
- }
- }
-
- #endregion
-
- #region VerifyThrowInvalidOperation
-
- ///
- /// Throws an InvalidOperationException with the specified resource string
- ///
- /// Resource to use in the exception
- /// Formatting args.
- internal static void ThrowInvalidOperation(string resourceName, params object[] args)
- {
-#if DEBUG
- ResourceUtilities.VerifyResourceStringExists(resourceName);
-#endif
- if (s_throwExceptions)
- {
- throw new InvalidOperationException(ResourceUtilities.FormatResourceString(resourceName, args));
- }
- }
-
- ///
- /// Throws an InvalidOperationException if the given condition is false.
- ///
- ///
- ///
- internal static void VerifyThrowInvalidOperation
- (
- bool condition,
- string resourceName
- )
- {
- if (!condition)
- {
- // PERF NOTE: explicitly passing null for the arguments array
- // prevents memory allocation
- ThrowInvalidOperation(resourceName, null);
- }
- }
-
- ///
- /// Overload for one string format argument.
- ///
- ///
- ///
- ///
- internal static void VerifyThrowInvalidOperation
- (
- bool condition,
- string resourceName,
- object arg0
- )
- {
- // PERF NOTE: check the condition here instead of pushing it into
- // the ThrowInvalidOperation() method, because that method always
- // allocates memory for its variable array of arguments
- if (!condition)
- {
- ThrowInvalidOperation(resourceName, arg0);
- }
- }
-
- ///
- /// Overload for two string format arguments.
- ///
- ///
- ///
- ///
- ///
- internal static void VerifyThrowInvalidOperation
- (
- bool condition,
- string resourceName,
- object arg0,
- object arg1
- )
- {
- // PERF NOTE: check the condition here instead of pushing it into
- // the ThrowInvalidOperation() method, because that method always
- // allocates memory for its variable array of arguments
- if (!condition)
- {
- ThrowInvalidOperation(resourceName, arg0, arg1);
- }
- }
-
- ///
- /// Overload for three string format arguments.
- ///
- ///
- ///
- ///
- ///
- ///
- internal static void VerifyThrowInvalidOperation
- (
- bool condition,
- string resourceName,
- object arg0,
- object arg1,
- object arg2
- )
- {
- // PERF NOTE: check the condition here instead of pushing it into
- // the ThrowInvalidOperation() method, because that method always
- // allocates memory for its variable array of arguments
- if (!condition)
- {
- ThrowInvalidOperation(resourceName, arg0, arg1, arg2);
- }
- }
-
- ///
- /// Overload for four string format arguments.
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- internal static void VerifyThrowInvalidOperation
- (
- bool condition,
- string resourceName,
- object arg0,
- object arg1,
- object arg2,
- object arg3
- )
- {
- // PERF NOTE: check the condition here instead of pushing it into
- // the ThrowInvalidOperation() method, because that method always
- // allocates memory for its variable array of arguments
- if (!condition)
- {
- ThrowInvalidOperation(resourceName, arg0, arg1, arg2, arg3);
- }
- }
-
- #endregion
-
- #region VerifyThrowArgument
-
- ///
- /// Throws an ArgumentException that can include an inner exception.
- ///
- /// PERF WARNING: calling a method that takes a variable number of arguments
- /// is expensive, because memory is allocated for the array of arguments -- do
- /// not call this method repeatedly in performance-critical scenarios
- ///
- internal static void ThrowArgument
- (
- string resourceName,
- params object[] args
- )
- {
- ThrowArgument(null, resourceName, args);
- }
-
- ///
- /// Throws an ArgumentException that can include an inner exception.
- ///
- /// PERF WARNING: calling a method that takes a variable number of arguments
- /// is expensive, because memory is allocated for the array of arguments -- do
- /// not call this method repeatedly in performance-critical scenarios
- ///
- ///
- /// This method is thread-safe.
- ///
- /// Can be null.
- ///
- ///
- private static void ThrowArgument
- (
- Exception innerException,
- string resourceName,
- params object[] args
- )
- {
-#if DEBUG
- ResourceUtilities.VerifyResourceStringExists(resourceName);
-#endif
- if (s_throwExceptions)
- {
- throw new ArgumentException(ResourceUtilities.FormatResourceString(resourceName, args), innerException);
- }
- }
-
- ///
- /// Throws an ArgumentException if the given condition is false.
- ///
- /// This method is thread-safe.
- ///
- ///
- internal static void VerifyThrowArgument
- (
- bool condition,
- string resourceName
- )
- {
- VerifyThrowArgument(condition, null, resourceName);
- }
-
- ///
- /// Overload for one string format argument.
- ///
- /// This method is thread-safe.
- ///
- ///
- ///
- internal static void VerifyThrowArgument
- (
- bool condition,
- string resourceName,
- object arg0
- )
- {
- VerifyThrowArgument(condition, null, resourceName, arg0);
- }
-
- ///
- /// Overload for two string format arguments.
- ///
- /// This method is thread-safe.
- ///
- ///
- ///
- ///
- internal static void VerifyThrowArgument
- (
- bool condition,
- string resourceName,
- object arg0,
- object arg1
- )
- {
- VerifyThrowArgument(condition, null, resourceName, arg0, arg1);
- }
-
- ///
- /// Overload for three string format arguments.
- ///
- /// This method is thread-safe.
- internal static void VerifyThrowArgument
- (
- bool condition,
- string resourceName,
- object arg0,
- object arg1,
- object arg2
- )
- {
- VerifyThrowArgument(condition, null, resourceName, arg0, arg1, arg2);
- }
-
- ///
- /// Overload for four string format arguments.
- ///
- /// This method is thread-safe.
- internal static void VerifyThrowArgument
- (
- bool condition,
- string resourceName,
- object arg0,
- object arg1,
- object arg2,
- object arg3
- )
- {
- VerifyThrowArgument(condition, null, resourceName, arg0, arg1, arg2, arg3);
- }
-
- ///
- /// Throws an ArgumentException that includes an inner exception, if
- /// the given condition is false.
- ///
- /// This method is thread-safe.
- ///
- /// Can be null.
- ///
- internal static void VerifyThrowArgument
- (
- bool condition,
- Exception innerException,
- string resourceName
- )
- {
- if (!condition)
- {
- // PERF NOTE: explicitly passing null for the arguments array
- // prevents memory allocation
- ThrowArgument(innerException, resourceName, null);
- }
- }
-
- ///
- /// Overload for one string format argument.
- ///
- /// This method is thread-safe.
- ///
- ///
- ///
- ///
- internal static void VerifyThrowArgument
- (
- bool condition,
- Exception innerException,
- string resourceName,
- object arg0
- )
- {
- // PERF NOTE: check the condition here instead of pushing it into
- // the ThrowArgument() method, because that method always allocates
- // memory for its variable array of arguments
- if (!condition)
- {
- ThrowArgument(innerException, resourceName, arg0);
- }
- }
-
- ///
- /// Overload for two string format arguments.
- ///
- /// This method is thread-safe.
- ///
- ///
- ///
- ///
- ///
- internal static void VerifyThrowArgument
- (
- bool condition,
- Exception innerException,
- string resourceName,
- object arg0,
- object arg1
- )
- {
- // PERF NOTE: check the condition here instead of pushing it into
- // the ThrowArgument() method, because that method always allocates
- // memory for its variable array of arguments
- if (!condition)
- {
- ThrowArgument(innerException, resourceName, arg0, arg1);
- }
- }
-
- ///
- /// Overload for three string format arguments.
- ///
- /// This method is thread-safe.
- internal static void VerifyThrowArgument
- (
- bool condition,
- Exception innerException,
- string resourceName,
- object arg0,
- object arg1,
- object arg2
- )
- {
- // PERF NOTE: check the condition here instead of pushing it into
- // the ThrowArgument() method, because that method always allocates
- // memory for its variable array of arguments
- if (!condition)
- {
- ThrowArgument(innerException, resourceName, arg0, arg1, arg2);
- }
- }
-
- ///
- /// Overload for four string format arguments.
- ///
- /// This method is thread-safe.
- internal static void VerifyThrowArgument
- (
- bool condition,
- Exception innerException,
- string resourceName,
- object arg0,
- object arg1,
- object arg2,
- object arg3
- )
- {
- // PERF NOTE: check the condition here instead of pushing it into
- // the ThrowArgument() method, because that method always allocates
- // memory for its variable array of arguments
- if (!condition)
- {
- ThrowArgument(innerException, resourceName, arg0, arg1, arg2, arg3);
- }
- }
-
- #endregion
-
- #region VerifyThrowArgumentXXX
-
- ///
- /// Throws an argument out of range exception.
- ///
- internal static void ThrowArgumentOutOfRange(string parameterName)
- {
- if (s_throwExceptions)
- {
- throw new ArgumentOutOfRangeException(parameterName);
- }
- }
-
- ///
- /// Throws an ArgumentOutOfRangeException using the given parameter name
- /// if the condition is false.
- ///
- internal static void VerifyThrowArgumentOutOfRange(bool condition, string parameterName)
- {
- if (!condition)
- {
- ThrowArgumentOutOfRange(parameterName);
- }
- }
-
- ///
- /// Throws an ArgumentNullException if the given string parameter is null
- /// and ArgumentException if it has zero length.
- ///
- ///
- ///
- internal static void VerifyThrowArgumentLength(string parameter, string parameterName)
- {
- VerifyThrowArgumentNull(parameter, parameterName);
-
- if (parameter.Length == 0 && s_throwExceptions)
- {
- throw new ArgumentException(ResourceUtilities.FormatResourceString("Shared.ParameterCannotHaveZeroLength", parameterName));
- }
- }
-
- ///
- /// Throws an ArgumentNullException if the given string parameter is null
- /// and ArgumentException if it has zero length.
- ///
- ///
- ///
- internal static void VerifyThrowArgumentInvalidPath(string parameter, string parameterName)
- {
- VerifyThrowArgumentNull(parameter, parameterName);
-
- if (FileUtilities.PathIsInvalid(parameter) && s_throwExceptions)
- {
- throw new ArgumentException(ResourceUtilities.FormatResourceString("Shared.ParameterCannotHaveInvalidPathChars", parameterName, parameter));
- }
- }
-
- ///
- /// Throws an ArgumentException if the string has zero length, unless it is
- /// null, in which case no exception is thrown.
- ///
- internal static void VerifyThrowArgumentLengthIfNotNull(string parameter, string parameterName)
- {
- if (parameter != null && parameter.Length == 0 && s_throwExceptions)
- {
- throw new ArgumentException(ResourceUtilities.FormatResourceString("Shared.ParameterCannotHaveZeroLength", parameterName));
- }
- }
-
- ///
- /// Throws an ArgumentNullException if the given parameter is null.
- ///
- /// This method is thread-safe.
- ///
- ///
- internal static void VerifyThrowArgumentNull(object parameter, string parameterName)
- {
- VerifyThrowArgumentNull(parameter, parameterName, "Shared.ParameterCannotBeNull");
- }
-
- ///
- /// Throws an ArgumentNullException if the given parameter is null.
- ///
- /// This method is thread-safe.
- internal static void VerifyThrowArgumentNull(object parameter, string parameterName, string resourceName)
- {
- if (parameter == null && s_throwExceptions)
- {
- // Most ArgumentNullException overloads append its own rather clunky multi-line message.
- // So use the one overload that doesn't.
- throw new ArgumentNullException(
- ResourceUtilities.FormatResourceString(resourceName, parameterName),
- (Exception)null);
- }
- }
-
- ///
- /// Verifies the given arrays are not null and have the same length
- ///
- ///
- ///
- ///
- ///
- internal static void VerifyThrowArgumentArraysSameLength(Array parameter1, Array parameter2, string parameter1Name, string parameter2Name)
- {
- VerifyThrowArgumentNull(parameter1, parameter1Name);
- VerifyThrowArgumentNull(parameter2, parameter2Name);
-
- if (parameter1.Length != parameter2.Length && s_throwExceptions)
- {
- throw new ArgumentException(ResourceUtilities.FormatResourceString("Shared.ParametersMustHaveTheSameLength", parameter1Name, parameter2Name));
- }
- }
-
- #endregion
-#endif
- }
-}
diff --git a/src/Ionide.ProjInfo.Sln/vendor/EscapingUtilities.cs b/src/Ionide.ProjInfo.Sln/vendor/EscapingUtilities.cs
deleted file mode 100644
index 0c08e3d7..00000000
--- a/src/Ionide.ProjInfo.Sln/vendor/EscapingUtilities.cs
+++ /dev/null
@@ -1,304 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Text;
-
-namespace Ionide.ProjInfo.Sln.Shared
-{
- ///
- /// This class implements static methods to assist with unescaping of %XX codes
- /// in the MSBuild file format.
- ///
- ///
- /// PERF: since we escape and unescape relatively frequently, it may be worth caching
- /// the last N strings that were (un)escaped
- ///
- static internal class EscapingUtilities
- {
- ///
- /// Optional cache of escaped strings for use when needing to escape in performance-critical scenarios with significant
- /// expected string reuse.
- ///
- private static Dictionary s_unescapedToEscapedStrings = new Dictionary(StringComparer.Ordinal);
-
- ///
- /// Replaces all instances of %XX in the input string with the character represented
- /// by the hexadecimal number XX.
- ///
- /// The string to unescape.
- /// unescaped string
- internal static string UnescapeAll
- (
- string escapedString
- )
- {
- bool throwAwayBool;
- return UnescapeAll(escapedString, out throwAwayBool);
- }
-
- private static bool IsHexDigit(char character)
- {
- return ((character >= '0') && (character <= '9'))
- || ((character >= 'A') && (character <= 'F'))
- || ((character >= 'a') && (character <= 'f'));
- }
-
- ///
- /// Replaces all instances of %XX in the input string with the character represented
- /// by the hexadecimal number XX.
- ///
- /// The string to unescape.
- /// Whether any replacements were made.
- /// unescaped string
- internal static string UnescapeAll
- (
- string escapedString,
- out bool escapingWasNecessary
- )
- {
- escapingWasNecessary = false;
-
- // If the string doesn't contain anything, then by definition it doesn't
- // need unescaping.
- if (String.IsNullOrEmpty(escapedString))
- {
- return escapedString;
- }
-
- // If there are no percent signs, just return the original string immediately.
- // Don't even instantiate the StringBuilder.
- int indexOfPercent = escapedString.IndexOf('%');
- if (indexOfPercent == -1)
- {
- return escapedString;
- }
-
- // This is where we're going to build up the final string to return to the caller.
- StringBuilder unescapedString = StringBuilderCache.Acquire(escapedString.Length);
-
- int currentPosition = 0;
-
- // Loop until there are no more percent signs in the input string.
- while (indexOfPercent != -1)
- {
- // There must be two hex characters following the percent sign
- // for us to even consider doing anything with this.
- if (
- (indexOfPercent <= (escapedString.Length - 3)) &&
- IsHexDigit(escapedString[indexOfPercent + 1]) &&
- IsHexDigit(escapedString[indexOfPercent + 2])
- )
- {
- // First copy all the characters up to the current percent sign into
- // the destination.
- unescapedString.Append(escapedString, currentPosition, indexOfPercent - currentPosition);
-
- // Convert the %XX to an actual real character.
- string hexString = escapedString.Substring(indexOfPercent + 1, 2);
- char unescapedCharacter = (char)int.Parse(hexString, System.Globalization.NumberStyles.HexNumber,
- CultureInfo.InvariantCulture);
-
- // if the unescaped character is not on the exception list, append it
- unescapedString.Append(unescapedCharacter);
-
- // Advance the current pointer to reflect the fact that the destination string
- // is up to date with everything up to and including this escape code we just found.
- currentPosition = indexOfPercent + 3;
-
- escapingWasNecessary = true;
- }
-
- // Find the next percent sign.
- indexOfPercent = escapedString.IndexOf('%', indexOfPercent + 1);
- }
-
- // Okay, there are no more percent signs in the input string, so just copy the remaining
- // characters into the destination.
- unescapedString.Append(escapedString, currentPosition, escapedString.Length - currentPosition);
-
- return StringBuilderCache.GetStringAndRelease(unescapedString);
- }
-
-
- ///
- /// Adds instances of %XX in the input string where the char to be escaped appears
- /// XX is the hex value of the ASCII code for the char. Interns and caches the result.
- ///
- ///
- /// NOTE: Only recommended for use in scenarios where there's expected to be significant
- /// repetition of the escaped string. Cache currently grows unbounded.
- ///
- internal static string EscapeWithCaching(string unescapedString)
- {
- return EscapeWithOptionalCaching(unescapedString, cache: true);
- }
-
- ///
- /// Adds instances of %XX in the input string where the char to be escaped appears
- /// XX is the hex value of the ASCII code for the char.
- ///
- /// The string to escape.
- /// escaped string
- internal static string Escape(string unescapedString)
- {
- return EscapeWithOptionalCaching(unescapedString, cache: false);
- }
-
- ///
- /// Adds instances of %XX in the input string where the char to be escaped appears
- /// XX is the hex value of the ASCII code for the char. Caches if requested.
- ///
- /// The string to escape.
- ///
- /// True if the cache should be checked, and if the resultant string
- /// should be cached.
- ///
- private static string EscapeWithOptionalCaching(string unescapedString, bool cache)
- {
- // If there are no special chars, just return the original string immediately.
- // Don't even instantiate the StringBuilder.
- if (String.IsNullOrEmpty(unescapedString) || !ContainsReservedCharacters(unescapedString))
- {
- return unescapedString;
- }
-
- // next, if we're caching, check to see if it's already there.
- if (cache)
- {
- string cachedEscapedString = null;
- lock (s_unescapedToEscapedStrings)
- {
- if (s_unescapedToEscapedStrings.TryGetValue(unescapedString, out cachedEscapedString))
- {
- return cachedEscapedString;
- }
- }
- }
-
- // This is where we're going to build up the final string to return to the caller.
- StringBuilder escapedStringBuilder = StringBuilderCache.Acquire(unescapedString.Length * 2);
-
- AppendEscapedString(escapedStringBuilder, unescapedString);
-
- if (!cache)
- {
- return StringBuilderCache.GetStringAndRelease(escapedStringBuilder);
- }
-
- string escapedString = OpportunisticIntern.StringBuilderToString(escapedStringBuilder);
- StringBuilderCache.Release(escapedStringBuilder);
-
- lock (s_unescapedToEscapedStrings)
- {
- s_unescapedToEscapedStrings[unescapedString] = escapedString;
- }
-
- return escapedString;
- }
-
- ///
- /// Before trying to actually escape the string, it can be useful to call this method to determine
- /// if escaping is necessary at all. This can save lots of calls to copy around item metadata
- /// that is really the same whether escaped or not.
- ///
- ///
- ///
- private static bool ContainsReservedCharacters
- (
- string unescapedString
- )
- {
- return (-1 != unescapedString.IndexOfAny(s_charsToEscape));
- }
-
- ///
- /// Determines whether the string contains the escaped form of '*' or '?'.
- ///
- ///
- ///
- internal static bool ContainsEscapedWildcards
- (
- string escapedString
- )
- {
- if (-1 != escapedString.IndexOf('%'))
- {
- // It has a '%' sign. We have promise.
- if (
- (-1 != escapedString.IndexOf("%2", StringComparison.Ordinal)) ||
- (-1 != escapedString.IndexOf("%3", StringComparison.Ordinal))
- )
- {
- // It has either a '%2' or a '%3'. This is looking very promising.
- return
- (
- (-1 != escapedString.IndexOf("%2a", StringComparison.Ordinal)) ||
- (-1 != escapedString.IndexOf("%2A", StringComparison.Ordinal)) ||
- (-1 != escapedString.IndexOf("%3f", StringComparison.Ordinal)) ||
- (-1 != escapedString.IndexOf("%3F", StringComparison.Ordinal))
- );
- }
- }
- return false;
- }
-
- ///
- /// Convert the given integer into its hexadecimal representation.
- ///
- /// The number to convert, which must be non-negative and less than 16
- /// The character which is the hexadecimal representation of .
- private static char HexDigitChar(int x)
- {
- return (char)(x + (x < 10 ? '0' : ('a' - 10)));
- }
-
- ///
- /// Append the escaped version of the given character to a .
- ///
- /// The to which to append.
- /// The character to escape.
- private static void AppendEscapedChar(StringBuilder sb, char ch)
- {
- // Append the escaped version which is a percent sign followed by two hexadecimal digits
- sb.Append('%');
- sb.Append(HexDigitChar(ch / 0x10));
- sb.Append(HexDigitChar(ch & 0x0F));
- }
-
- ///
- /// Append the escaped version of the given string to a .
- ///
- /// The to which to append.
- /// The unescaped string.
- private static void AppendEscapedString(StringBuilder sb, string unescapedString)
- {
- // Replace each unescaped special character with an escape sequence one
- for (int idx = 0; ;)
- {
- int nextIdx = unescapedString.IndexOfAny(s_charsToEscape, idx);
- if (nextIdx == -1)
- {
- sb.Append(unescapedString, idx, unescapedString.Length - idx);
- break;
- }
-
- sb.Append(unescapedString, idx, nextIdx - idx);
- AppendEscapedChar(sb, unescapedString[nextIdx]);
- idx = nextIdx + 1;
- }
- }
-
- ///
- /// Special characters that need escaping.
- /// It's VERY important that the percent character is the FIRST on the list - since it's both a character
- /// we escape and use in escape sequences, we can unintentionally escape other escape sequences if we
- /// don't process it first. Of course we'll have a similar problem if we ever decide to escape hex digits
- /// (that would require rewriting the algorithm) but since it seems unlikely that we ever do, this should
- /// be good enough to avoid complicating the algorithm at this point.
- ///
- private static readonly char[] s_charsToEscape = { '%', '*', '?', '@', '$', '(', ')', ';', '\'' };
- }
-}
diff --git a/src/Ionide.ProjInfo.Sln/vendor/InvalidProjectFileException.cs b/src/Ionide.ProjInfo.Sln/vendor/InvalidProjectFileException.cs
deleted file mode 100644
index 826b4f45..00000000
--- a/src/Ionide.ProjInfo.Sln/vendor/InvalidProjectFileException.cs
+++ /dev/null
@@ -1,382 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-using System;
-using System.IO;
-using System.Xml;
-using System.Runtime.Serialization;
-#if FEATURE_SECURITY_PERMISSIONS
-using System.Security.Permissions;
-#endif
-
-using Ionide.ProjInfo.Sln.Shared;
-using Ionide.ProjInfo.Sln.Evaluation;
-
-namespace Ionide.ProjInfo.Sln.Exceptions
-{
- ///
- /// This exception is thrown whenever there is a problem with the user's XML project file. The problem might be semantic or
- /// syntactical. The latter would be of a type typically caught by XSD validation (if it was performed by the project writer).
- ///
- ///
- /// WARNING: marking a type [Serializable] without implementing ISerializable imposes a serialization contract -- it is a
- /// promise to never change the type's fields i.e. the type is immutable; adding new fields in the next version of the type
- /// without following certain special FX guidelines, can break both forward and backward compatibility
- ///
- [Serializable]
- public sealed class InvalidProjectFileException : Exception
- {
- #region Basic constructors
-
- ///
- /// Default constructor.
- ///
- ///
- /// This constructor only exists to satisfy .NET coding guidelines. Use a rich constructor whenever possible.
- ///
- public InvalidProjectFileException()
- : base()
- {
- // do nothing
- }
-
- ///
- /// Creates an instance of this exception using the specified error message.
- ///
- ///
- /// This constructor only exists to satisfy .NET coding guidelines. Use a rich constructor whenever possible.
- ///
- ///
- public InvalidProjectFileException(string message)
- : base(message)
- {
- // do nothing
- }
-
- ///
- /// Creates an instance of this exception using the specified error message and inner exception.
- ///
- ///
- /// This constructor only exists to satisfy .NET coding guidelines. Use a rich constructor whenever possible.
- ///
- ///
- ///
- public InvalidProjectFileException(string message, Exception innerException)
- : base(message, innerException)
- {
- // do nothing
- }
-
- ///
- /// Creates an instance of this exception using the specified error message and inner invalid project file exception.
- /// This is used in order to wrap and exception rather than rethrow it verbatim, which would reset the callstack.
- /// The assumption is that all the metadata for the outer exception comes from the inner exception, eg., they have the same error code.
- ///
- internal InvalidProjectFileException(string message, InvalidProjectFileException innerException)
- : this(innerException.ProjectFile, innerException.LineNumber, innerException.ColumnNumber, innerException.EndLineNumber, innerException.EndColumnNumber, message, innerException.ErrorSubcategory, innerException.ErrorCode, innerException.HelpKeyword)
- {
- }
-
- #endregion
-
-#if FEATURE_BINARY_SERIALIZATION
- #region Serialization (update when adding new class members)
-
- ///
- /// Protected constructor used for (de)serialization.
- /// If we ever add new members to this class, we'll need to update this.
- ///
- ///
- ///
- private InvalidProjectFileException(SerializationInfo info, StreamingContext context)
- : base(info, context)
- {
- file = info.GetString("projectFile");
- lineNumber = info.GetInt32("lineNumber");
- columnNumber = info.GetInt32("columnNumber");
- endLineNumber = info.GetInt32("endLineNumber");
- endColumnNumber = info.GetInt32("endColumnNumber");
- errorSubcategory = info.GetString("errorSubcategory");
- errorCode = info.GetString("errorCode");
- helpKeyword = info.GetString("helpKeyword");
- hasBeenLogged = info.GetBoolean("hasBeenLogged");
- }
-
- ///
- /// ISerializable method which we must override since Exception implements this interface
- /// If we ever add new members to this class, we'll need to update this.
- ///
- ///
- ///
-#if FEATURE_SECURITY_PERMISSIONS
- [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
-#endif
- override public void GetObjectData(SerializationInfo info, StreamingContext context)
- {
- base.GetObjectData(info, context);
-
- info.AddValue("projectFile", file);
- info.AddValue("lineNumber", lineNumber);
- info.AddValue("columnNumber", columnNumber);
- info.AddValue("endLineNumber", endLineNumber);
- info.AddValue("endColumnNumber", endColumnNumber);
- info.AddValue("errorSubcategory", errorSubcategory);
- info.AddValue("errorCode", errorCode);
- info.AddValue("helpKeyword", helpKeyword);
- info.AddValue("hasBeenLogged", hasBeenLogged);
- }
-
- #endregion
-#endif
-
- #region Rich constructors
-
- ///
- /// Creates an instance of this exception using rich error information.
- ///
- /// This constructor is preferred over the basic constructors.
- /// The invalid project file (can be empty string).
- /// The invalid line number in the project (set to zero if not available).
- /// The invalid column number in the project (set to zero if not available).
- /// The end of a range of invalid lines in the project (set to zero if not available).
- /// The end of a range of invalid columns in the project (set to zero if not available).
- /// Error message for exception.
- /// Error sub-category that describes the error (can be null).
- /// The error code (can be null).
- /// The F1-help keyword for the host IDE (can be null).
- public InvalidProjectFileException
- (
- string projectFile,
- int lineNumber,
- int columnNumber,
- int endLineNumber,
- int endColumnNumber,
- string message,
- string errorSubcategory,
- string errorCode,
- string helpKeyword
- ) :
- this(projectFile, lineNumber, columnNumber, endLineNumber, endColumnNumber, message, errorSubcategory, errorCode, helpKeyword, null)
- {
- }
-
- ///
- /// Creates an instance of this exception using rich error information.
- ///
- /// This constructor is preferred over the basic constructors.
- /// The invalid project file (can be empty string).
- /// The invalid line number in the project (set to zero if not available).
- /// The invalid column number in the project (set to zero if not available).
- /// The end of a range of invalid lines in the project (set to zero if not available).
- /// The end of a range of invalid columns in the project (set to zero if not available).
- /// Error message for exception.
- /// Error sub-category that describes the error (can be null).
- /// The error code (can be null).
- /// The F1-help keyword for the host IDE (can be null).
- /// Any inner exception. May be null.
- internal InvalidProjectFileException
- (
- string projectFile,
- int lineNumber,
- int columnNumber,
- int endLineNumber,
- int endColumnNumber,
- string message,
- string errorSubcategory,
- string errorCode,
- string helpKeyword,
- Exception innerException
- ) : base(message, innerException)
- {
- ErrorUtilities.VerifyThrowArgumentNull(projectFile, "projectFile");
- ErrorUtilities.VerifyThrowArgumentLength(message, "message");
-
- // Try to helpfully provide a full path if possible, but do so robustly.
- // This exception might be because the path was invalid!
- // Also don't consider "MSBUILD" a path: that's what msbuild.exe uses when there's no project associated.
- if (projectFile.Length > 0 && !String.Equals(projectFile, "MSBUILD", StringComparison.Ordinal))
- {
- string fullPath = FileUtilities.GetFullPathNoThrow(projectFile);
-
- projectFile = (fullPath == null) ? projectFile : fullPath;
- }
-
- file = projectFile;
- this.lineNumber = lineNumber;
- this.columnNumber = columnNumber;
- this.endLineNumber = endLineNumber;
- this.endColumnNumber = endColumnNumber;
- this.errorSubcategory = errorSubcategory;
- this.errorCode = errorCode;
- this.helpKeyword = helpKeyword;
- }
-
- #endregion
-
- #region Properties
-
- ///
- /// Gets the exception message including the affected project file (if any).
- ///
- /// The complete message string.
- public override string Message
- {
- get
- {
- return base.Message + ((!String.IsNullOrEmpty(ProjectFile))
- ? (" " + ProjectFile)
- : null);
- }
- }
-
- ///
- /// Gets the exception message not including the project file.
- ///
- /// The error message string only.
- public string BaseMessage
- {
- get
- {
- return base.Message;
- }
- }
-
- ///
- /// Gets the file (if any) associated with this exception.
- /// This may be an imported file.
- ///
- ///
- /// The name is poorly chosen as this may be a targets file,
- /// but the name has shipped now.
- ///
- /// Project filename/path string, or null.
- public string ProjectFile
- {
- get
- {
- return file;
- }
- }
-
- ///
- /// Gets the invalid line number (if any) in the project.
- ///
- /// The invalid line number, or zero.
- public int LineNumber
- {
- get
- {
- return lineNumber;
- }
- }
-
- ///
- /// Gets the invalid column number (if any) in the project.
- ///
- /// The invalid column number, or zero.
- public int ColumnNumber
- {
- get
- {
- return columnNumber;
- }
- }
-
- ///
- /// Gets the last line number (if any) of a range of invalid lines in the project.
- ///
- /// The last invalid line number, or zero.
- public int EndLineNumber
- {
- get
- {
- return endLineNumber;
- }
- }
-
- ///
- /// Gets the last column number (if any) of a range of invalid columns in the project.
- ///
- /// The last invalid column number, or zero.
- public int EndColumnNumber
- {
- get
- {
- return endColumnNumber;
- }
- }
-
- ///
- /// Gets the error sub-category (if any) that describes the type of this error.
- ///
- /// The sub-category string, or null.
- public string ErrorSubcategory
- {
- get
- {
- return errorSubcategory;
- }
- }
-
- ///
- /// Gets the error code (if any) associated with the exception message.
- ///
- /// Error code string, or null.
- public string ErrorCode
- {
- get
- {
- return errorCode;
- }
- }
-
- ///
- /// Gets the F1-help keyword (if any) associated with this error, for the host IDE.
- ///
- /// The keyword string, or null.
- public string HelpKeyword
- {
- get
- {
- return helpKeyword;
- }
- }
-
- ///
- /// Whether the exception has already been logged. Allows the exception to be logged at the
- /// most appropriate location, but continue to be propagated.
- ///
- public bool HasBeenLogged
- {
- get
- {
- return hasBeenLogged;
- }
- internal set
- {
- hasBeenLogged = value;
- }
- }
-
- #endregion
-
- // the project file that caused this exception
- private string file;
- // the invalid line number in the project
- private int lineNumber;
- // the invalid column number in the project
- private int columnNumber;
- // the end of a range of invalid lines in the project
- private int endLineNumber;
- // the end of a range of invalid columns in the project
- private int endColumnNumber;
- // the error sub-category that describes the type of this error
- private string errorSubcategory;
- // the error code for the exception message
- private string errorCode;
- // the F1-help keyword for the host IDE
- private string helpKeyword;
- // Has this errors been sent to the loggers?
- private bool hasBeenLogged = false;
- }
-}
diff --git a/src/Ionide.ProjInfo.Sln/vendor/OpportunisticIntern.cs b/src/Ionide.ProjInfo.Sln/vendor/OpportunisticIntern.cs
deleted file mode 100644
index 7438019c..00000000
--- a/src/Ionide.ProjInfo.Sln/vendor/OpportunisticIntern.cs
+++ /dev/null
@@ -1,1219 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-//-----------------------------------------------------------------------
-//
-// Selectively intern strings.
-//-----------------------------------------------------------------------
-
-using System;
-#if !CLR2COMPATIBILITY
-using System.Collections.Concurrent;
-#endif
-using System.Text;
-using System.Linq;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Globalization;
-using Ionide.ProjInfo.Sln.Shared;
-using Ionide.ProjInfo.Sln.Utilities;
-#if MICROSOFT_BUILD_TASKS
-using MSBuildConstants = Microsoft.Build.Tasks.MSBuildConstants;
-#else
-using MSBuildConstants = Ionide.ProjInfo.Sln.Shared.MSBuildConstants;
-#endif
-
-
-namespace Ionide.ProjInfo.Sln
-{
- ///
- /// This class is used to selectively intern strings. It should be used at the point of new string creation.
- /// For example,
- ///
- /// string interned = OpportunisticIntern.Intern(String.Join(",",someStrings));
- ///
- /// This class uses heuristics to decide whether it will be efficient to intern a string or not. There is no
- /// guarantee that a string will intern.
- ///
- /// The thresholds and sizes were determined by experimentation to give the best number of bytes saved
- /// at reasonable elapsed time cost.
- ///
- static internal class OpportunisticIntern
- {
- private static readonly bool s_useSimpleConcurrency = Traits.Instance.UseSimpleInternConcurrency;
-
- ///
- /// The size of the small mru list.
- ///
- private static readonly int s_smallMruSize = AssignViaEnvironment("MSBUILDSMALLINTERNSIZE", 50);
-
- ///
- /// The size of the large mru list.
- ///
- private static readonly int s_largeMruSize = AssignViaEnvironment("MSBUILDLARGEINTERNSIZE", 100);
-
- ///
- /// The size of the huge mru list.
- ///
- private static readonly int s_hugeMruSize = AssignViaEnvironment("MSBUILDHUGEINTERNSIZE", 100);
-
- ///
- /// The smallest size a string can be to be considered small.
- ///
- private static readonly int s_smallMruThreshhold = AssignViaEnvironment("MSBUILDSMALLINTERNTHRESHOLD", 50);
-
- ///
- /// The smallest size a string can be to be considered large.
- ///
- private static readonly int s_largeMruThreshhold = AssignViaEnvironment("MSBUILDLARGEINTERNTHRESHOLD", 70);
-
- ///
- /// The smallest size a string can be to be considered huge.
- ///
- private static readonly int s_hugeMruThreshhold = AssignViaEnvironment("MSBUILDHUGEINTERNTHRESHOLD", 200);
-
- ///
- /// The smallest size a string can be to be ginormous.
- /// 8K for large object heap.
- ///
- private static readonly int s_ginormousThreshhold = AssignViaEnvironment("MSBUILDGINORMOUSINTERNTHRESHOLD", 8000);
-
- ///
- /// Manages the separate MRU lists.
- ///
- private static BucketedPrioritizedStringList s_si = new BucketedPrioritizedStringList(/*gatherStatistics*/ false, s_smallMruSize, s_largeMruSize, s_hugeMruSize, s_smallMruThreshhold, s_largeMruThreshhold, s_hugeMruThreshhold, s_ginormousThreshhold, s_useSimpleConcurrency);
-
- #region Statistics
- ///
- /// What if Mru lists were infinitely long?
- ///
- private static BucketedPrioritizedStringList s_whatIfInfinite;
-
- ///
- /// What if we doubled the size of the Mru lists?
- ///
- private static BucketedPrioritizedStringList s_whatIfDoubled;
-
- ///
- /// What if we halved the size of the Mru lists?
- ///
- private static BucketedPrioritizedStringList s_whatIfHalved;
-
- ///
- /// What if the size of Mru lists was zero? (We still intern tiny strings in this case)
- ///
- private static BucketedPrioritizedStringList s_whatIfZero;
- #endregion
-
- #region IInternable
- ///
- /// Define the methods needed to intern something.
- ///
- internal interface IInternable
- {
- ///
- /// The length of the target.
- ///
- int Length { get; }
-
- ///
- /// Indexer into the target. Presumed to be fast.
- ///
- char this[int index]
- {
- get;
- }
-
- ///
- /// Convert target to string. Presumed to be slow (and will be called just once).
- ///
- string ExpensiveConvertToString();
-
- ///
- /// Compare target to string. Assumes lengths are equal.
- ///
- bool IsOrdinalEqualToStringOfSameLength(string other);
-
- ///
- /// Reference compare target to string. If target is non-string this should return false.
- ///
- bool ReferenceEquals(string other);
- }
- #endregion
-
- ///
- /// Assign an int from an environment variable. If its not present, use the default.
- ///
- static internal int AssignViaEnvironment(string env, int @default)
- {
- string threshhold = Environment.GetEnvironmentVariable(env);
- if (!String.IsNullOrEmpty(threshhold))
- {
- int result;
- if (Int32.TryParse(threshhold, out result))
- {
- return result;
- }
- }
-
- return @default;
- }
-
- ///
- /// Turn on statistics gathering.
- ///
- internal static void EnableStatisticsGathering()
- {
- // Statistics include several 'what if' scenarios such as doubling the size of the MRU lists.
- s_si = new BucketedPrioritizedStringList(/*gatherStatistics*/ true, s_smallMruSize, s_largeMruSize, s_hugeMruSize, s_smallMruThreshhold, s_largeMruThreshhold, s_hugeMruThreshhold, s_ginormousThreshhold, s_useSimpleConcurrency);
- s_whatIfInfinite = new BucketedPrioritizedStringList(/*gatherStatistics*/ true, Int32.MaxValue, Int32.MaxValue, Int32.MaxValue, s_smallMruThreshhold, s_largeMruThreshhold, s_hugeMruThreshhold, s_ginormousThreshhold, s_useSimpleConcurrency);
- s_whatIfDoubled = new BucketedPrioritizedStringList(/*gatherStatistics*/ true, s_smallMruSize * 2, s_largeMruSize * 2, s_hugeMruSize * 2, s_smallMruThreshhold, s_largeMruThreshhold, s_hugeMruThreshhold, s_ginormousThreshhold, s_useSimpleConcurrency);
- s_whatIfHalved = new BucketedPrioritizedStringList(/*gatherStatistics*/ true, s_smallMruSize / 2, s_largeMruSize / 2, s_hugeMruSize / 2, s_smallMruThreshhold, s_largeMruThreshhold, s_hugeMruThreshhold, s_ginormousThreshhold, s_useSimpleConcurrency);
- s_whatIfZero = new BucketedPrioritizedStringList(/*gatherStatistics*/ true, 0, 0, 0, s_smallMruThreshhold, s_largeMruThreshhold, s_hugeMruThreshhold, s_ginormousThreshhold, s_useSimpleConcurrency);
- }
-
- ///
- /// Intern the given internable.
- ///
- internal static string InternableToString(IInternable candidate)
- {
- if (s_whatIfInfinite != null)
- {
- s_whatIfInfinite.InterningToString(candidate);
- s_whatIfDoubled.InterningToString(candidate);
- s_whatIfHalved.InterningToString(candidate);
- s_whatIfZero.InterningToString(candidate);
- }
-
- string result = s_si.InterningToString(candidate);
-#if _DEBUG
- string expected = candidate.ExpensiveConvertToString();
- if (!String.Equals(result, expected))
- {
- ErrorUtilities.ThrowInternalError("Interned string {0} should have been {1}", result, expected);
- }
-#endif
- return result;
- }
-
- ///
- /// Potentially Intern the given string builder.
- ///
- internal static string StringBuilderToString(StringBuilder candidate)
- {
- return InternableToString(new StringBuilderInternTarget(candidate));
- }
-
- ///
- /// Potentially Intern the given char array.
- ///
- internal static string CharArrayToString(char[] candidate, int count)
- {
- return InternableToString(new CharArrayInternTarget(candidate, count));
- }
-
- ///
- /// Potentially Intern the given char array.
- ///
- internal static string CharArrayToString(char[] candidate, int startIndex, int count)
- {
- return InternableToString(new CharArrayInternTarget(candidate, startIndex, count));
- }
-
- ///
- /// Potentially Intern the given string.
- ///
- /// The string to intern.
- /// The interned string, or the same string if it could not be interned.
- internal static string InternStringIfPossible(string candidate)
- {
- return InternableToString(new StringInternTarget(candidate));
- }
-
- ///
- /// Report statistics about interning. Don't call unless GatherStatistics has been called beforehand.
- ///
- internal static void ReportStatistics()
- {
- s_si.ReportStatistics("Main");
- s_whatIfInfinite.ReportStatistics("if Infinite");
- s_whatIfDoubled.ReportStatistics("if Doubled");
- s_whatIfHalved.ReportStatistics("if Halved");
- s_whatIfZero.ReportStatistics("if Zero");
- Console.WriteLine(" * Even for MRU size of zero there will still be some intern hits because of the tiny ");
- Console.WriteLine(" string matching (eg. 'true')");
- }
-
- #region IInternable Implementations
- ///
- /// A wrapper over StringBuilder.
- ///
- internal struct StringBuilderInternTarget : IInternable
- {
- ///
- /// The held StringBuilder
- ///
- private StringBuilder _target;
-
- ///
- /// Pointless comment about constructor.
- ///
- internal StringBuilderInternTarget(StringBuilder target)
- {
- _target = target;
- }
-
- ///
- /// The length of the target.
- ///
- public int Length
- {
- get
- {
- return _target.Length;
- }
- }
-
- ///
- /// Indexer into the target. Presumed to be fast.
- ///
- public char this[int index]
- {
- get
- {
- return _target[index];
- }
- }
-
- ///
- /// Never reference equals to string.
- ///
- public bool ReferenceEquals(string other)
- {
- return false;
- }
-
- ///
- /// Convert target to string. Presumed to be slow (and will be called just once).
- ///
- public string ExpensiveConvertToString()
- {
- // PERF NOTE: This will be an allocation hot-spot because the StringBuilder is finally determined to
- // not be internable. There is still only one conversion of StringBuilder into string it has just
- // moved into this single spot.
- return _target.ToString();
- }
-
- ///
- /// Compare target to string. Assumes lengths are equal.
- ///
- public bool IsOrdinalEqualToStringOfSameLength(string other)
- {
-#if DEBUG
- ErrorUtilities.VerifyThrow(other.Length == _target.Length, "should be same length");
-#endif
- int length = _target.Length;
-
- // Backwards because the end of the string is (by observation of Australian Government build) more likely to be different earlier in the loop.
- // For example, C:\project1, C:\project2
- for (int i = length - 1; i >= 0; --i)
- {
- if (_target[i] != other[i])
- {
- return false;
- }
- }
-
- return true;
- }
-
- ///
- /// Don't use this function. Use ExpensiveConvertToString
- ///
- public override string ToString()
- {
- throw new InvalidOperationException();
- }
- }
-
- ///
- /// A wrapper over char[].
- ///
- internal struct CharArrayInternTarget : IInternable
- {
- ///
- /// Start index for the string
- ///
- private int _startIndex;
-
- ///
- /// Number of characters.
- ///
- private int _count;
-
- ///
- /// The held array
- ///
- private char[] _target;
-
- ///
- /// Pointless comment about constructor.
- ///
- internal CharArrayInternTarget(char[] target, int count)
- : this(target, 0, count)
- {
- }
-
- ///
- /// Pointless comment about constructor.
- ///
- internal CharArrayInternTarget(char[] target, int startIndex, int count)
- {
-#if DEBUG
- if (startIndex + count > target.Length)
- {
- ErrorUtilities.ThrowInternalError("wrong length");
- }
-#endif
- _target = target;
- _startIndex = startIndex;
- _count = count;
- }
-
- ///
- /// The length of the target.
- ///
- public int Length
- {
- get
- {
- return _count;
- }
- }
-
- ///
- /// Indexer into the target. Presumed to be fast.
- ///
- public char this[int index]
- {
- get
- {
- if (index > _startIndex + _count - 1 || index < 0)
- {
- ErrorUtilities.ThrowInternalError("past end");
- }
-
- return _target[index + _startIndex];
- }
- }
-
- ///
- /// Convert target to string. Presumed to be slow (and will be called just once).
- ///
- public bool ReferenceEquals(string other)
- {
- return false;
- }
-
- ///
- /// Convert target to string. Presumed to be slow (and will be called just once).
- ///
- public string ExpensiveConvertToString()
- {
- // PERF NOTE: This will be an allocation hot-spot because the char[] is finally determined to
- // not be internable. There is still only one conversion of char[] into string it has just
- // moved into this single spot.
- return new String(_target, _startIndex, _count);
- }
-
- ///
- /// Compare target to string. Assumes lengths are equal.
- ///
- public bool IsOrdinalEqualToStringOfSameLength(string other)
- {
-#if DEBUG
- ErrorUtilities.VerifyThrow(other.Length == this.Length, "should be same length");
-#endif
- // Backwards because the end of the string is (by observation of Australian Government build) more likely to be different earlier in the loop.
- // For example, C:\project1, C:\project2
- for (int i = _count - 1; i >= 0; --i)
- {
- if (_target[i + _startIndex] != other[i])
- {
- return false;
- }
- }
-
- return true;
- }
-
- ///
- /// Don't use this function. Use ExpensiveConvertToString
- ///
- public override string ToString()
- {
- throw new InvalidOperationException();
- }
- }
-
- ///
- /// Wrapper over a string.
- ///
- internal struct StringInternTarget : IInternable
- {
- ///
- /// Stores the wrapped string.
- ///
- private string _target;
-
- ///
- /// Constructor of the class
- ///
- /// The string to wrap
- internal StringInternTarget(string target)
- {
- ErrorUtilities.VerifyThrowArgumentLength(target, "target");
- _target = target;
- }
-
- ///
- /// Gets the length of the target string.
- ///
- public int Length
- {
- get { return _target.Length; }
- }
-
- ///
- /// Gets the n character in the target string.
- ///
- /// Index of the character to gather.
- /// The character in the position marked by index.
- public char this[int index]
- {
- get { return _target[index]; }
- }
-
- ///
- /// Returns the target which is already a string.
- ///
- /// The target string.
- public string ExpensiveConvertToString()
- {
- return _target;
- }
-
- ///
- /// Compare if the target string is equal to the given string.
- ///
- /// The string to compare with the target.
- /// True if the strings are equal, false otherwise.
- public bool IsOrdinalEqualToStringOfSameLength(string other)
- {
- return _target.Equals(other, StringComparison.Ordinal);
- }
-
- ///
- /// Verifies if the reference of the target string is the same of the given string.
- ///
- /// The string reference to compare to.
- /// True if both references are equal, false otherwise.
- public bool ReferenceEquals(string other)
- {
- return Object.ReferenceEquals(_target, other);
- }
- }
-
- #endregion
-
- ///
- /// Manages a set of mru lists that hold strings in varying size ranges.
- ///
- private class BucketedPrioritizedStringList
- {
- ///
- /// The small string Mru list.
- ///
- private PrioritizedStringList _smallMru;
-
- ///
- /// The large string Mru list.
- ///
- private PrioritizedStringList _largeMru;
-
- ///
- /// The huge string Mru list.
- ///
- private PrioritizedStringList _hugeMru;
-
- ///
- /// Three most recently used strings over 8K.
- ///
- private LinkedList _ginormous = new LinkedList();
-
- ///
- /// The smallest size a string can be to be considered small.
- ///
- private int _smallMruThreshhold;
-
- ///
- /// The smallest size a string can be to be considered large.
- ///
- private int _largeMruThreshhold;
-
- ///
- /// The smallest size a string can be to be considered huge.
- ///
- private int _hugeMruThreshhold;
-
- ///
- /// The smallest size a string can be to be ginormous.
- ///
- private int _ginormousThreshhold;
-
- private readonly bool _useSimpleConcurrency;
-
-#if !CLR2COMPATIBILITY
- private ConcurrentDictionary _internedStrings = new ConcurrentDictionary(StringComparer.Ordinal);
-#endif
-
- #region Statistics
- ///
- /// Whether or not to gather statistics
- ///
- private bool _gatherStatistics = false;
-
- ///
- /// Number of times interning worked.
- ///
- private int _internHits;
-
- ///
- /// Number of times interning didn't work.
- ///
- private int _internMisses;
-
- ///
- /// Number of times interning wasn't attempted.
- ///
- private int _internRejects;
-
- ///
- /// Total number of strings eliminated by interning.
- ///
- private int _internEliminatedStrings;
-
- ///
- /// Total number of chars eliminated across all strings.
- ///
- private int _internEliminatedChars;
-
- ///
- /// Number of times the ginourmous string hit.
- ///
- private int _ginormousHits;
-
- ///
- /// Number of times the ginourmous string missed.
- ///
- private int _ginormousMisses;
-
- ///
- /// Chars interned for ginormous range.
- ///
- private int _ginormousCharsSaved;
-
- ///
- /// Whether or not to track ginormous strings.
- ///
- private bool _dontTrack;
-
- ///
- /// The time spent interning.
- ///
- private Stopwatch _stopwatch;
-
- ///
- /// Strings which did not intern
- ///
- private Dictionary _missedStrings;
-
- ///
- /// Strings which we didn't attempt to intern
- ///
- private Dictionary _rejectedStrings;
-
- ///
- /// Number of ginormous strings to keep
- /// By observation of Auto7, there are about three variations of the huge solution config blob
- /// There aren't really any other strings of this size, but make it 10 to be sure. (There will barely be any misses)
- ///
- private int _ginormousSize = 10;
-
- #endregion
-
- ///
- /// Construct.
- ///
- internal BucketedPrioritizedStringList(bool gatherStatistics, int smallMruSize, int largeMruSize, int hugeMruSize, int smallMruThreshhold, int largeMruThreshhold, int hugeMruThreshhold, int ginormousThreshhold, bool useSimpleConcurrency)
- {
- if (smallMruSize == 0 && largeMruSize == 0 && hugeMruSize == 0)
- {
- _dontTrack = true;
- }
-
- _smallMru = new PrioritizedStringList(smallMruSize);
- _largeMru = new PrioritizedStringList(largeMruSize);
- _hugeMru = new PrioritizedStringList(hugeMruSize);
- _smallMruThreshhold = smallMruThreshhold;
- _largeMruThreshhold = largeMruThreshhold;
- _hugeMruThreshhold = hugeMruThreshhold;
- _ginormousThreshhold = ginormousThreshhold;
- _useSimpleConcurrency = useSimpleConcurrency;
-
- for (int i = 0; i < _ginormousSize; i++)
- {
- _ginormous.AddFirst(new WeakReference(String.Empty));
- }
-
- _gatherStatistics = gatherStatistics;
- if (gatherStatistics)
- {
- _stopwatch = new Stopwatch();
- _missedStrings = new Dictionary(StringComparer.Ordinal);
- _rejectedStrings = new Dictionary(StringComparer.Ordinal);
- }
- }
-
- ///
- /// Intern the given internable.
- ///
- internal string InterningToString(IInternable candidate)
- {
- if (candidate.Length == 0)
- {
- // As in the case that a property or itemlist has evaluated to empty.
- return String.Empty;
- }
-
- if (_gatherStatistics)
- {
- return InternWithStatistics(candidate);
- }
- else
- {
- string result;
- TryIntern(candidate, out result);
- return result;
- }
- }
-
- ///
- /// Report statistics to the console.
- ///
- internal void ReportStatistics(string heading)
- {
- string title = "Opportunistic Intern (" + heading + ")";
- Console.WriteLine("\n{0}{1}{0}", new String('=', 41 - (title.Length / 2)), title);
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Intern Hits", _internHits, "hits");
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Intern Misses", _internMisses, "misses");
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Intern Rejects (as shorter than " + s_smallMruThreshhold + " bytes)", _internRejects, "rejects");
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Eliminated Strings*", _internEliminatedStrings, "strings");
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Eliminated Chars", _internEliminatedChars, "chars");
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Estimated Eliminated Bytes", _internEliminatedChars * 2, "bytes");
- Console.WriteLine("Elimination assumes that strings provided were unique objects.");
- Console.WriteLine("|---------------------------------------------------------------------------------|");
- KeyValuePair held = _smallMru.Statistics();
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Small Strings MRU Size", s_smallMruSize, "strings");
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Small Strings (>=" + _smallMruThreshhold + " chars) Held", held.Key, "strings");
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Small Estimated Bytes Held", held.Value * 2, "bytes");
- Console.WriteLine("|---------------------------------------------------------------------------------|");
- held = _largeMru.Statistics();
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Large Strings MRU Size", s_largeMruSize, "strings");
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Large Strings (>=" + _largeMruThreshhold + " chars) Held", held.Key, "strings");
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Large Estimated Bytes Held", held.Value * 2, "bytes");
- Console.WriteLine("|---------------------------------------------------------------------------------|");
- held = _hugeMru.Statistics();
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Huge Strings MRU Size", s_hugeMruSize, "strings");
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Huge Strings (>=" + _hugeMruThreshhold + " chars) Held", held.Key, "strings");
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Huge Estimated Bytes Held", held.Value * 2, "bytes");
- Console.WriteLine("|---------------------------------------------------------------------------------|");
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Ginormous Strings MRU Size", _ginormousSize, "strings");
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Ginormous (>=" + _ginormousThreshhold + " chars) Hits", _ginormousHits, "hits");
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Ginormous Misses", _ginormousMisses, "misses");
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Ginormous Chars Saved", _ginormousCharsSaved, "chars");
- Console.WriteLine("|---------------------------------------------------------------------------------|");
-
- // There's no point in reporting the ginormous string because it will have evaporated by now.
- Console.WriteLine("||{0,50}|{1,20:N0}|{2,8}|", "Time Spent Interning", _stopwatch.ElapsedMilliseconds, "ms");
- Console.WriteLine("{0}{0}", new String('=', 41));
-
- var topMissingString =
- _missedStrings
- .OrderByDescending(kv => kv.Value * kv.Key.Length)
- .Take(15)
- .Where(kv => kv.Value > 1)
- .Select(kv => String.Format(CultureInfo.InvariantCulture, "({1} instances x each {2} chars = {3}KB wasted)\n{0}", kv.Key, kv.Value, kv.Key.Length, (kv.Value - 1) * kv.Key.Length * 2 / 1024));
-
- Console.WriteLine("##########Top Missed Strings: \n{0} ", String.Join("\n==============\n", topMissingString.ToArray()));
- Console.WriteLine();
-
- var topRejectedString =
- _rejectedStrings
- .OrderByDescending(kv => kv.Value * kv.Key.Length)
- .Take(15)
- .Where(kv => kv.Value > 1)
- .Select(kv => String.Format(CultureInfo.InvariantCulture, "({1} instances x each {2} chars = {3}KB wasted)\n{0}", kv.Key, kv.Value, kv.Key.Length, (kv.Value - 1) * kv.Key.Length * 2 / 1024));
-
- Console.WriteLine("##########Top Rejected Strings: \n{0} ", String.Join("\n==============\n", topRejectedString.ToArray()));
- }
-
- ///
- /// Try to intern the string.
- /// Return true if an interned value could be returned.
- /// Return false if it was added to the intern list, but wasn't there already.
- /// Return null if it didn't meet the length criteria for any of the buckets. Interning was rejected
- ///
- private bool? TryIntern(IInternable candidate, out string interned)
- {
- int length = candidate.Length;
-
- // First, try the hard coded intern strings.
- // Each of the hard-coded small strings below showed up in a profile run with considerable duplication in memory.
- if (!_dontTrack)
- {
- if (length == 2)
- {
- if (candidate[1] == '#')
- {
- if (candidate[0] == 'C')
- {
- interned = "C#";
- return true;
- }
-
- if (candidate[0] == 'F')
- {
- interned = "F#";
- return true;
- }
- }
-
- if (candidate[0] == 'V' && candidate[1] == 'B')
- {
- interned = "VB";
- return true;
- }
- }
- else if (length == 4)
- {
- if (candidate[0] == 'T')
- {
- if (candidate[1] == 'R' && candidate[2] == 'U' && candidate[3] == 'E')
- {
- interned = "TRUE";
- return true;
- }
-
- if (candidate[1] == 'r' && candidate[2] == 'u' && candidate[3] == 'e')
- {
- interned = "True";
- return true;
- }
- }
-
- if (candidate[0] == 'C' && candidate[1] == 'o' && candidate[2] == 'p' && candidate[3] == 'y')
- {
- interned = "Copy";
- return true;
- }
-
- if (candidate[0] == 't' && candidate[1] == 'r' && candidate[2] == 'u' && candidate[3] == 'e')
- {
- interned = "true";
- return true;
- }
-
- if (candidate[0] == 'v' && candidate[1] == '4' && candidate[2] == '.' && candidate[3] == '0')
- {
- interned = "v4.0";
- return true;
- }
- }
- else if (length == 5)
- {
- if (candidate[0] == 'F' && candidate[1] == 'A' && candidate[2] == 'L' && candidate[3] == 'S' && candidate[4] == 'E')
- {
- interned = "FALSE";
- return true;
- }
-
- if (candidate[0] == 'f' && candidate[1] == 'a' && candidate[2] == 'l' && candidate[3] == 's' && candidate[4] == 'e')
- {
- interned = "false";
- return true;
- }
-
- if (candidate[0] == 'D' && candidate[1] == 'e' && candidate[2] == 'b' && candidate[3] == 'u' && candidate[4] == 'g')
- {
- interned = "Debug";
- return true;
- }
-
- if (candidate[0] == 'B' && candidate[1] == 'u' && candidate[2] == 'i' && candidate[3] == 'l' && candidate[4] == 'd')
- {
- interned = "Build";
- return true;
- }
-
- if (candidate[0] == 'W' && candidate[1] == 'i' && candidate[2] == 'n' && candidate[3] == '3' && candidate[4] == '2')
- {
- interned = "Win32";
- return true;
- }
- }
- else if (length == 6)
- {
- if (candidate[0] == '\'' && candidate[1] == '\'' && candidate[2] == '!' && candidate[3] == '=' && candidate[4] == '\'' && candidate[5] == '\'')
- {
- interned = "''!=''";
- return true;
- }
-
- if (candidate[0] == 'A' && candidate[1] == 'n' && candidate[2] == 'y' && candidate[3] == 'C' && candidate[4] == 'P' && candidate[5] == 'U')
- {
- interned = "AnyCPU";
- return true;
- }
- }
- else if (length == 7)
- {
- if (candidate[0] == 'L' && candidate[1] == 'i' && candidate[2] == 'b' && candidate[3] == 'r' && candidate[4] == 'a' && candidate[5] == 'r' && candidate[6] == 'y')
- {
- interned = "Library";
- return true;
- }
-
- if (candidate[0] == 'M' && candidate[1] == 'S' && candidate[2] == 'B' && candidate[3] == 'u' && candidate[4] == 'i' && candidate[5] == 'l' && candidate[6] == 'd')
- {
- interned = "MSBuild";
- return true;
- }
-
- if (candidate[0] == 'R' && candidate[1] == 'e' && candidate[2] == 'l' && candidate[3] == 'e' && candidate[4] == 'a' && candidate[5] == 's' && candidate[6] == 'e')
- {
- interned = "Release";
- return true;
- }
- }
- // see Microsoft.Build.BackEnd.BuildRequestConfiguration.CreateUniqueGlobalProperty
- else if (length > MSBuildConstants.MSBuildDummyGlobalPropertyHeader.Length &&
- candidate[0] == 'M' &&
- candidate[1] == 'S' &&
- candidate[2] == 'B' &&
- candidate[3] == 'u' &&
- candidate[4] == 'i' &&
- candidate[5] == 'l' &&
- candidate[6] == 'd' &&
- candidate[7] == 'P' &&
- candidate[8] == 'r' &&
- candidate[9] == 'o' &&
- candidate[10] == 'j' &&
- candidate[11] == 'e' &&
- candidate[12] == 'c' &&
- candidate[13] == 't' &&
- candidate[14] == 'I' &&
- candidate[15] == 'n' &&
- candidate[16] == 's' &&
- candidate[17] == 't' &&
- candidate[18] == 'a' &&
- candidate[19] == 'n' &&
- candidate[20] == 'c' &&
- candidate[21] == 'e'
- )
- {
- // don't want to leak unique strings into the cache
- interned = candidate.ExpensiveConvertToString();
- return null;
- }
- else if (length == 24)
- {
- if (candidate[0] == 'R' && candidate[1] == 'e' && candidate[2] == 's' && candidate[3] == 'o' && candidate[4] == 'l' && candidate[5] == 'v' && candidate[6] == 'e')
- {
- if (candidate[7] == 'A' && candidate[8] == 's' && candidate[9] == 's' && candidate[10] == 'e' && candidate[11] == 'm' && candidate[12] == 'b' && candidate[13] == 'l' && candidate[14] == 'y')
- {
- if (candidate[15] == 'R' && candidate[16] == 'e' && candidate[17] == 'f' && candidate[18] == 'e' && candidate[19] == 'r' && candidate[20] == 'e' && candidate[21] == 'n' && candidate[22] == 'c' && candidate[23] == 'e')
- {
- interned = "ResolveAssemblyReference";
- return true;
- }
- }
- }
- }
- else if (length > _ginormousThreshhold)
- {
- lock (_ginormous)
- {
- LinkedListNode current = _ginormous.First;
-
- while (current != null)
- {
- string last = current.Value.Target as string;
- if (last != null && last.Length == candidate.Length && candidate.IsOrdinalEqualToStringOfSameLength(last))
- {
- interned = last;
- _ginormousHits++;
- _ginormousCharsSaved += last.Length;
-
- _ginormous.Remove(current);
- _ginormous.AddFirst(current);
-
- return true;
- }
-
- current = current.Next;
- }
-
- _ginormousMisses++;
- interned = candidate.ExpensiveConvertToString();
-
- var lastNode = _ginormous.Last;
- _ginormous.RemoveLast();
- _ginormous.AddFirst(lastNode);
- lastNode.Value.Target = interned;
-
- return false;
- }
- }
-#if !CLR2COMPATIBILITY
- else if (_useSimpleConcurrency)
- {
- var stringified = candidate.ExpensiveConvertToString();
- interned = _internedStrings.GetOrAdd(stringified, stringified);
- return true;
- }
-#endif
- else if (length >= _hugeMruThreshhold)
- {
- lock (_hugeMru)
- {
- return _hugeMru.TryGet(candidate, out interned);
- }
- }
- else if (length >= _largeMruThreshhold)
- {
- lock (_largeMru)
- {
- return _largeMru.TryGet(candidate, out interned);
- }
- }
- else if (length >= _smallMruThreshhold)
- {
- lock (_smallMru)
- {
- return _smallMru.TryGet(candidate, out interned);
- }
- }
- }
-
- interned = candidate.ExpensiveConvertToString();
- return null;
- }
-
- ///
- /// Version of Intern that gathers statistics
- ///
- private string InternWithStatistics(IInternable candidate)
- {
- string result;
- _stopwatch.Start();
- bool? interned = TryIntern(candidate, out result);
- _stopwatch.Stop();
-
- if (interned.HasValue && !interned.Value)
- {
- // Could not intern.
- _internMisses++;
-
- int priorCount = 0;
- _missedStrings.TryGetValue(result, out priorCount);
- _missedStrings[result] = priorCount + 1;
-
- return result;
- }
- else if (interned == null)
- {
- // Decided not to attempt interning
- _internRejects++;
-
- int priorCount = 0;
- _rejectedStrings.TryGetValue(result, out priorCount);
- _rejectedStrings[result] = priorCount + 1;
-
- return result;
- }
-
- _internHits++;
- if (!candidate.ReferenceEquals(result))
- {
- // Reference changed so 'candidate' is now released and should save memory.
- _internEliminatedStrings++;
- _internEliminatedChars += candidate.Length;
- }
-
- return result;
- }
-
- ///
- /// A singly linked list of strings where the most recently accessed string is at the top.
- /// Size expands up to a fixed number of strings.
- ///
- private class PrioritizedStringList
- {
- ///
- /// Maximum size of the mru list.
- ///
- private readonly int _size;
-
- ///
- /// Head of the mru list.
- ///
- private Node _mru;
-
- ///
- /// Construct an Mru list with a fixed maximum size.
- ///
- internal PrioritizedStringList(int size)
- {
- _size = size;
- }
-
- ///
- /// Try to get one element from the list. Upon leaving the function 'candidate' will be at the head of the Mru list.
- /// This function is not thread-safe.
- ///
- internal bool TryGet(IInternable candidate, out string interned)
- {
- if (_size == 0)
- {
- interned = candidate.ExpensiveConvertToString();
- return false;
- }
-
- int length = candidate.Length;
- Node secondPrior = null;
- Node prior = null;
- Node head = _mru;
- bool found = false;
- int itemCount = 0;
-
- while (head != null && !found)
- {
- if (head.Value.Length == length)
- {
- if (candidate.IsOrdinalEqualToStringOfSameLength(head.Value))
- {
- found = true;
- }
- }
-
- if (!found)
- {
- secondPrior = prior;
- prior = head;
- head = head.Next;
- }
-
- itemCount++;
- }
-
- if (found)
- {
- // Move it to the top and return the interned version.
- if (prior != null)
- {
- if (!candidate.ReferenceEquals(head.Value))
- {
- // Wasn't at the top already, so move it there.
- prior.Next = head.Next;
- head.Next = _mru;
- _mru = head;
- interned = _mru.Value;
- return true;
- }
- else
- {
- // But don't move it up if there is reference equality so that multiple calls to Intern don't redundantly emphasize a string.
- interned = head.Value;
- return true;
- }
- }
- else
- {
- // Found the item in the top spot. No need to move anything.
- interned = _mru.Value;
- return true;
- }
- }
- else
- {
- // Not found. Create a new entry and place it at the top.
- Node old = _mru;
- _mru = new Node(candidate.ExpensiveConvertToString());
- _mru.Next = old;
-
- // Cache miss. Use this opportunity to discard any element over the max size.
- if (itemCount >= _size && secondPrior != null)
- {
- secondPrior.Next = null;
- }
-
- interned = _mru.Value;
- return false;
- }
- }
-
- ///
- /// Returns the number of strings held and the total number of chars held.
- ///
- internal KeyValuePair Statistics()
- {
- Node head = _mru;
- int chars = 0;
- int strings = 0;
- while (head != null)
- {
- chars += head.Value.Length;
- strings++;
- head = head.Next;
- }
-
- return new KeyValuePair(strings, chars);
- }
-
- ///
- /// Singly linked list node.
- ///
- private class Node
- {
- ///
- /// Construct a Node
- ///
- internal Node(string value)
- {
- Value = value;
- }
-
- ///
- /// The next node in the list.
- ///
- internal Node Next { get; set; }
-
- ///
- /// The held string.
- ///
- internal string Value { get; private set; }
- }
- }
- }
- }
-}
diff --git a/src/Ionide.ProjInfo.Sln/vendor/ProjectConfigurationInSolution.cs b/src/Ionide.ProjInfo.Sln/vendor/ProjectConfigurationInSolution.cs
deleted file mode 100644
index f2b1ac6e..00000000
--- a/src/Ionide.ProjInfo.Sln/vendor/ProjectConfigurationInSolution.cs
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-using System;
-
-namespace Ionide.ProjInfo.Sln.Construction
-{
- ///
- /// This class represents an entry for a project configuration in a solution configuration.
- ///
- public sealed class ProjectConfigurationInSolution
- {
- ///
- /// Constructor
- ///
- internal ProjectConfigurationInSolution(string configurationName, string platformName, bool includeInBuild)
- {
- ConfigurationName = configurationName;
- PlatformName = RemoveSpaceFromAnyCpuPlatform(platformName);
- IncludeInBuild = includeInBuild;
- FullName = SolutionConfigurationInSolution.ComputeFullName(ConfigurationName, PlatformName);
- }
-
- ///
- /// The configuration part of this configuration - e.g. "Debug", "Release"
- ///
- public string ConfigurationName { get; }
-
- ///
- /// The platform part of this configuration - e.g. "Any CPU", "Win32"
- ///
- public string PlatformName { get; }
-
- ///
- /// The full name of this configuration - e.g. "Debug|Any CPU"
- ///
- public string FullName { get; }
-
- ///
- /// True if this project configuration should be built as part of its parent solution configuration
- ///
- public bool IncludeInBuild { get; }
-
- ///
- /// This is a hacky method to remove the space in the "Any CPU" platform in project configurations.
- /// The problem is that this platform is stored as "AnyCPU" in project files, but the project system
- /// reports it as "Any CPU" to the solution configuration manager. Because of that all solution configurations
- /// contain the version with a space in it, and when we try and give that name to actual projects,
- /// they have no clue what we're talking about. We need to remove the space in project platforms so that
- /// the platform name matches the one used in projects.
- ///
- private static string RemoveSpaceFromAnyCpuPlatform(string platformName)
- {
- if (string.Equals(platformName, "Any CPU", StringComparison.OrdinalIgnoreCase))
- {
- return "AnyCPU";
- }
-
- return platformName;
- }
- }
-}
diff --git a/src/Ionide.ProjInfo.Sln/vendor/ProjectFileErrorUtilities.cs b/src/Ionide.ProjInfo.Sln/vendor/ProjectFileErrorUtilities.cs
deleted file mode 100644
index b0517f4c..00000000
--- a/src/Ionide.ProjInfo.Sln/vendor/ProjectFileErrorUtilities.cs
+++ /dev/null
@@ -1,154 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-using System;
-using System.Xml;
-
-using InvalidProjectFileException = Ionide.ProjInfo.Sln.Exceptions.InvalidProjectFileException;
-
-namespace Ionide.ProjInfo.Sln.Shared
-{
- ///
- /// This class contains methods that are useful for error checking and validation of project files.
- ///
- static internal class ProjectFileErrorUtilities
- {
- ///
- /// This method is used to flag errors in the project file being processed. Do NOT use this method in place of
- /// ErrorUtilities.VerifyThrow(), because ErrorUtilities.VerifyThrow() is used to flag internal/programming errors.
- ///
- /// PERF WARNING: calling a method that takes a variable number of arguments is expensive, because memory is allocated for
- /// the array of arguments -- do not call this method repeatedly in performance-critical scenarios
- ///
- /// The invalid project file.
- /// The resource string for the error message.
- /// Extra arguments for formatting the error message.
- internal static void ThrowInvalidProjectFile
- (
- BuildEventFileInfo projectFile,
- string resourceName,
- params object[] args
- )
- {
- VerifyThrowInvalidProjectFile(false, null, projectFile, resourceName, args);
- }
-
- ///
- /// This method is used to flag errors in the project file being processed. Do NOT use this method in place of
- /// ErrorUtilities.VerifyThrow(), because ErrorUtilities.VerifyThrow() is used to flag internal/programming errors.
- ///
- /// PERF WARNING: calling a method that takes a variable number of arguments is expensive, because memory is allocated for
- /// the array of arguments -- do not call this method repeatedly in performance-critical scenarios
- ///
- /// The invalid project file.
- /// Any inner exception. May be null.
- /// The resource string for the error message.
- /// Extra arguments for formatting the error message.
- internal static void ThrowInvalidProjectFile
- (
- BuildEventFileInfo projectFile,
- Exception innerException,
- string resourceName,
- params object[] args
- )
- {
- VerifyThrowInvalidProjectFile(false, null, projectFile, innerException, resourceName, args);
- }
-
- ///
- /// This method is used to flag errors in the project file being processed. Do NOT use this method in place of
- /// ErrorUtilities.VerifyThrow(), because ErrorUtilities.VerifyThrow() is used to flag internal/programming errors.
- ///
- /// PERF WARNING: calling a method that takes a variable number of arguments is expensive, because memory is allocated for
- /// the array of arguments -- do not call this method repeatedly in performance-critical scenarios
- ///
- /// The condition to check.
- /// The invalid project file.
- /// The resource string for the error message.
- /// Extra arguments for formatting the error message.
- internal static void VerifyThrowInvalidProjectFile
- (
- bool condition,
- BuildEventFileInfo projectFile,
- string resourceName,
- params object[] args
- )
- {
- VerifyThrowInvalidProjectFile(condition, null, projectFile, resourceName, args);
- }
-
- ///
- /// This method is used to flag errors in the project file being processed. Do NOT use this method in place of
- /// ErrorUtilities.VerifyThrow(), because ErrorUtilities.VerifyThrow() is used to flag internal/programming errors.
- ///
- /// PERF WARNING: calling a method that takes a variable number of arguments is expensive, because memory is allocated for
- /// the array of arguments -- do not call this method repeatedly in performance-critical scenarios
- ///
- /// The condition to check.
- /// The resource string for the error sub-category (can be null).
- /// The invalid project file.
- /// The resource string for the error message.
- /// Extra arguments for formatting the error message.
- internal static void VerifyThrowInvalidProjectFile
- (
- bool condition,
- string errorSubCategoryResourceName,
- BuildEventFileInfo projectFile,
- string resourceName,
- params object[] args
- )
- {
- VerifyThrowInvalidProjectFile(condition, errorSubCategoryResourceName, projectFile, null, resourceName, args);
- }
-
- ///
- /// This method is used to flag errors in the project file being processed. Do NOT use this method in place of
- /// ErrorUtilities.VerifyThrow(), because ErrorUtilities.VerifyThrow() is used to flag internal/programming errors.
- ///
- /// PERF WARNING: calling a method that takes a variable number of arguments is expensive, because memory is allocated for
- /// the array of arguments -- do not call this method repeatedly in performance-critical scenarios
- ///
- /// The condition to check.
- /// The resource string for the error sub-category (can be null).
- /// The invalid project file.
- /// The inner .
- /// The resource string for the error message.
- /// Extra arguments for formatting the error message.
- internal static void VerifyThrowInvalidProjectFile
- (
- bool condition,
- string errorSubCategoryResourceName,
- BuildEventFileInfo projectFile,
- Exception innerException,
- string resourceName,
- params object[] args
- )
- {
- ErrorUtilities.VerifyThrow(projectFile != null, "Must specify the invalid project file. If project file is not available, use VerifyThrowInvalidProject() and pass in the XML node instead.");
-
-#if DEBUG
- if (errorSubCategoryResourceName != null)
- {
- ResourceUtilities.VerifyResourceStringExists(errorSubCategoryResourceName);
- }
-
- ResourceUtilities.VerifyResourceStringExists(resourceName);
-#endif
- if (!condition)
- {
- string errorSubCategory = null;
-
- if (errorSubCategoryResourceName != null)
- {
- errorSubCategory = AssemblyResources.GetString(errorSubCategoryResourceName);
- }
-
- string errorCode;
- string helpKeyword;
- string message = ResourceUtilities.FormatResourceString(out errorCode, out helpKeyword, resourceName, args);
-
- throw new InvalidProjectFileException(projectFile.File, projectFile.Line, projectFile.Column, projectFile.EndLine, projectFile.EndColumn, message, errorSubCategory, errorCode, helpKeyword, innerException);
- }
- }
- }
-}
diff --git a/src/Ionide.ProjInfo.Sln/vendor/ProjectInSolution.cs b/src/Ionide.ProjInfo.Sln/vendor/ProjectInSolution.cs
deleted file mode 100644
index efd00c56..00000000
--- a/src/Ionide.ProjInfo.Sln/vendor/ProjectInSolution.cs
+++ /dev/null
@@ -1,557 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.IO;
-using System.Security;
-using System.Text;
-using System.Xml;
-using System.Collections.ObjectModel;
-using System.Linq;
-using Ionide.ProjInfo.Sln.Shared;
-
-namespace Ionide.ProjInfo.Sln.Construction
-{
- ///
- /// An enumeration defining the different types of projects we might find in an SLN.
- ///
- public enum SolutionProjectType
- {
- ///
- /// Everything else besides the below well-known project types.
- ///
- Unknown,
- ///
- /// C#, VB, F#, and VJ# projects
- ///
- KnownToBeMSBuildFormat,
- ///
- /// Solution folders appear in the .sln file, but aren't buildable projects.
- ///
- SolutionFolder,
- ///
- /// ASP.NET projects
- ///
- WebProject,
- ///
- /// Web Deployment (.wdproj) projects
- ///
- WebDeploymentProject, // MSBuildFormat, but Whidbey-era ones specify ProjectReferences differently
- ///
- /// Project inside an Enterprise Template project
- ///
- EtpSubProject,
- ///
- /// A shared project represents a collection of shared files that is not buildable on its own.
- ///
- SharedProject
- }
-
- internal struct AspNetCompilerParameters
- {
- internal string aspNetVirtualPath; // For Venus projects only, Virtual path for web
- internal string aspNetPhysicalPath; // For Venus projects only, Physical path for web
- internal string aspNetTargetPath; // For Venus projects only, Target for output files
- internal string aspNetForce; // For Venus projects only, Force overwrite of target
- internal string aspNetUpdateable; // For Venus projects only, compiled web application is updateable
- internal string aspNetDebug; // For Venus projects only, generate symbols, etc.
- internal string aspNetKeyFile; // For Venus projects only, strong name key file.
- internal string aspNetKeyContainer; // For Venus projects only, strong name key container.
- internal string aspNetDelaySign; // For Venus projects only, delay sign strong name.
- internal string aspNetAPTCA; // For Venus projects only, AllowPartiallyTrustedCallers.
- internal string aspNetFixedNames; // For Venus projects only, generate fixed assembly names.
- }
-
- ///
- /// This class represents a project (or SLN folder) that is read in from a solution file.
- ///
- public sealed class ProjectInSolution
- {
- #region Constants
-
- ///
- /// Characters that need to be cleansed from a project name.
- ///
- private static readonly char[] s_charsToCleanse = { '%', '$', '@', ';', '.', '(', ')', '\'' };
-
- ///
- /// Project names that need to be disambiguated when forming a target name
- ///
- internal static readonly string[] projectNamesToDisambiguate = { "Build", "Rebuild", "Clean", "Publish" };
-
- ///
- /// Character that will be used to replace 'unclean' ones.
- ///
- private const char cleanCharacter = '_';
-
- #endregion
- #region Member data
- private string _relativePath; // Relative from .SLN file. For example, "WindowsApplication1\WindowsApplication1.csproj"
- private string _absolutePath; // Absolute path to the project file
- private readonly List _dependencies; // A list of strings representing the Guids of the dependent projects.
- private IReadOnlyList _dependenciesAsReadonly;
- private string _uniqueProjectName; // For example, "MySlnFolder\MySubSlnFolder\Windows_Application1"
- private string _originalProjectName; // For example, "MySlnFolder\MySubSlnFolder\Windows.Application1"
-
- private List _files;
-
- ///
- /// The project configuration in given solution configuration
- /// K: full solution configuration name (cfg + platform)
- /// V: project configuration
- ///
- private readonly Dictionary _projectConfigurations;
- private IReadOnlyDictionary _projectConfigurationsReadOnly;
-
- #endregion
-
- #region Constructors
-
- internal ProjectInSolution(SolutionFile solution)
- {
- ProjectType = SolutionProjectType.Unknown;
- ProjectName = null;
- _relativePath = null;
- ProjectGuid = null;
- _dependencies = new List();
- ParentProjectGuid = null;
- _uniqueProjectName = null;
- ParentSolution = solution;
- _files = new List();
-
- // default to .NET Framework 3.5 if this is an old solution that doesn't explicitly say.
- TargetFrameworkMoniker = ".NETFramework,Version=v3.5";
-
- // This hashtable stores a AspNetCompilerParameters struct for each configuration name supported.
- AspNetConfigurations = new Hashtable(StringComparer.OrdinalIgnoreCase);
-
- _projectConfigurations = new Dictionary(StringComparer.OrdinalIgnoreCase);
- }
-
- #endregion
-
- #region Properties
-
- ///
- /// This project's name
- ///
- public string ProjectName { get; internal set; }
-
- ///
- /// The path to this project file, relative to the solution location
- ///
- public string RelativePath
- {
- get { return _relativePath; }
- internal set
- {
- // Avoid loading System.Runtime.InteropServices.RuntimeInformation in full-framework
- // cases. It caused https://github.com/NuGet/Home/issues/6918.
- _relativePath = value;
- }
- }
-
- ///
- ///
- public void AddFolderFile(string path)
- {
- _files.Add(path);
- }
-
- ///
- /// Returns the absolute path for this project
- ///
- public string AbsolutePath
- {
- get
- {
- if (_absolutePath == null)
- {
- _absolutePath = Path.Combine(ParentSolution.SolutionFileDirectory, _relativePath);
-
- // For web site projects, Visual Studio stores the URL of the site as the relative path so it cannot be normalized.
- // Legacy behavior dictates that we must just return the result of Path.Combine()
- if (!Uri.TryCreate(_relativePath, UriKind.Absolute, out Uri _))
- {
- try
- {
- _absolutePath = Path.GetFullPath(_absolutePath);
- }
- catch (Exception)
- {
- // The call to GetFullPath() can throw if the relative path is some unsupported value or the paths are too long for the current file system
- // This falls back to previous behavior of returning a path that may not be correct but at least returns some value
- }
- }
- }
-
- return _absolutePath;
- }
- }
-
- ///
- /// The unique guid associated with this project, in "{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}" form
- ///
- public string ProjectGuid { get; internal set; }
-
- ///
- /// The guid, in "{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}" form, of this project's
- /// parent project, if any.
- ///
- public string ParentProjectGuid { get; internal set; }
-
- ///
- /// List of guids, in "{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}" form, mapping to projects
- /// that this project has a build order dependency on, as defined in the solution file.
- ///
- public IReadOnlyList Dependencies => _dependenciesAsReadonly ?? (_dependenciesAsReadonly = _dependencies.AsReadOnly());
-
- ///
- /// Configurations for this project, keyed off the configuration's full name, e.g. "Debug|x86"
- /// They contain only the project configurations from the solution file that fully matched (configuration and platform) against the solution configurations.
- ///
- public IReadOnlyDictionary ProjectConfigurations
- =>
- _projectConfigurationsReadOnly
- ?? (_projectConfigurationsReadOnly = new ReadOnlyDictionary(_projectConfigurations));
-
- ///
- /// Extension of the project file, if any
- ///
- internal string Extension => Path.GetExtension(_relativePath);
-
- ///
- /// This project's type.
- ///
- public SolutionProjectType ProjectType { get; set; }
-
- ///
- /// Only applies to websites -- for other project types, references are
- /// either specified as Dependencies above, or as ProjectReferences in the
- /// project file, which the solution doesn't have insight into.
- ///
- internal List ProjectReferences { get; } = new List();
-
- internal SolutionFile ParentSolution { get; set; }
-
- // Key is configuration name, value is [struct] AspNetCompilerParameters
- internal Hashtable AspNetConfigurations { get; set; }
-
- internal string TargetFrameworkMoniker { get; set; }
-
- #endregion
-
- #region Methods
-
- private bool _checkedIfCanBeMSBuildProjectFile;
- private bool _canBeMSBuildProjectFile;
- private string _canBeMSBuildProjectFileErrorMessage;
-
- ///
- /// Add the guid of a referenced project to our dependencies list.
- ///
- internal void AddDependency(string referencedProjectGuid)
- {
- _dependencies.Add(referencedProjectGuid);
- _dependenciesAsReadonly = null;
- }
-
- ///
- /// Set the requested project configuration.
- ///
- internal void SetProjectConfiguration(string configurationName, ProjectConfigurationInSolution configuration)
- {
- _projectConfigurations[configurationName] = configuration;
- _projectConfigurationsReadOnly = null;
- }
-
- ///
- /// Looks at the project file node and determines (roughly) if the project file is in the MSBuild format.
- /// The results are cached in case this method is called multiple times.
- ///
- /// Detailed error message in case we encounter critical problems reading the file
- ///
- internal bool CanBeMSBuildProjectFile(out string errorMessage)
- {
- if (_checkedIfCanBeMSBuildProjectFile)
- {
- errorMessage = _canBeMSBuildProjectFileErrorMessage;
- return _canBeMSBuildProjectFile;
- }
-
- _checkedIfCanBeMSBuildProjectFile = true;
- _canBeMSBuildProjectFile = false;
- errorMessage = null;
-
- try
- {
- // Read project thru a XmlReader with proper setting to avoid DTD processing
- var xrSettings = new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore };
- var projectDocument = new XmlDocument();
-
- using (XmlReader xmlReader = XmlReader.Create(AbsolutePath, xrSettings))
- {
- // Load the project file and get the first node
- projectDocument.Load(xmlReader);
- }
-
- XmlElement mainProjectElement = null;
-
- // The XML parser will guarantee that we only have one real root element,
- // but we need to find it amongst the other types of XmlNode at the root.
- foreach (XmlNode childNode in projectDocument.ChildNodes)
- {
- if (childNode.NodeType == XmlNodeType.Element)
- {
- mainProjectElement = (XmlElement)childNode;
- break;
- }
- }
-
- if (mainProjectElement?.LocalName == "Project")
- {
- // MSBuild supports project files with an empty (supported in Visual Studio 2017) or the default MSBuild
- // namespace.
- bool emptyNamespace = string.IsNullOrEmpty(mainProjectElement.NamespaceURI);
- bool defaultNamespace = String.Equals(mainProjectElement.NamespaceURI,
- "http://schemas.microsoft.com/developer/msbuild/2003",
- StringComparison.OrdinalIgnoreCase);
- bool projectElementInvalid = ElementContainsInvalidNamespaceDefitions(mainProjectElement);
-
- // If the MSBuild namespace is declared, it is very likely an MSBuild project that should be built.
- if (defaultNamespace)
- {
- _canBeMSBuildProjectFile = true;
- return _canBeMSBuildProjectFile;
- }
-
- // This is a bit of a special case, but an rptproj file will contain a Project with no schema that is
- // not an MSBuild file. It will however have ToolsVersion="2.0" which is not supported with an empty
- // schema. This is not a great solution, but it should cover the customer reported issue. See:
- // https://github.com/dotnet/msbuild/issues/2064
- if (emptyNamespace && !projectElementInvalid && mainProjectElement.GetAttribute("ToolsVersion") != "2.0")
- {
- _canBeMSBuildProjectFile = true;
- return _canBeMSBuildProjectFile;
- }
- }
- }
- // catch all sorts of exceptions - if we encounter any problems here, we just assume the project file is not
- // in the MSBuild format
-
- // handle errors in path resolution
- catch (SecurityException e)
- {
- _canBeMSBuildProjectFileErrorMessage = e.Message;
- }
- // handle errors in path resolution
- catch (NotSupportedException e)
- {
- _canBeMSBuildProjectFileErrorMessage = e.Message;
- }
- // handle errors in loading project file
- catch (IOException e)
- {
- _canBeMSBuildProjectFileErrorMessage = e.Message;
- }
- // handle errors in loading project file
- catch (UnauthorizedAccessException e)
- {
- _canBeMSBuildProjectFileErrorMessage = e.Message;
- }
- // handle XML parsing errors (when reading project file)
- // this is not critical, since the project file doesn't have to be in XML formal
- catch (XmlException)
- {
- }
-
- errorMessage = _canBeMSBuildProjectFileErrorMessage;
-
- return _canBeMSBuildProjectFile;
- }
-
- ///
- ///
- public IReadOnlyList FolderFiles => _files;
-
- ///
- /// Find the unique name for this project, e.g. SolutionFolder\SubSolutionFolder\Project_Name
- ///
- internal string GetUniqueProjectName()
- {
- if (_uniqueProjectName == null)
- {
- // EtpSubProject and Venus projects have names that are already unique. No need to prepend the SLN folder.
- if ((ProjectType == SolutionProjectType.WebProject) || (ProjectType == SolutionProjectType.EtpSubProject))
- {
- _uniqueProjectName = CleanseProjectName(ProjectName);
- }
- else
- {
- // This is "normal" project, which in this context means anything non-Venus and non-EtpSubProject.
-
- // If this project has a parent SLN folder, first get the full unique name for the SLN folder,
- // and tack on trailing backslash.
- string uniqueName = String.Empty;
-
- if (ParentProjectGuid != null)
- {
- if (!ParentSolution.ProjectsByGuid.TryGetValue(ParentProjectGuid, out ProjectInSolution proj))
- {
- ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(proj != null, "SubCategoryForSolutionParsingErrors",
- new BuildEventFileInfo(ParentSolution.FullPath), "SolutionParseNestedProjectErrorWithNameAndGuid", ProjectName, ProjectGuid, ParentProjectGuid);
- }
-
- uniqueName = proj.GetUniqueProjectName() + "\\";
- }
-
- // Now tack on our own project name, and cache it in the ProjectInSolution object for future quick access.
- _uniqueProjectName = CleanseProjectName(uniqueName + ProjectName);
- }
- }
-
- return _uniqueProjectName;
- }
-
- ///
- /// Gets the original project name with the parent project as it is declared in the solution file, e.g. SolutionFolder\SubSolutionFolder\Project.Name
- ///
- internal string GetOriginalProjectName()
- {
- if (_originalProjectName == null)
- {
- // EtpSubProject and Venus projects have names that are already unique. No need to prepend the SLN folder.
- if ((ProjectType == SolutionProjectType.WebProject) || (ProjectType == SolutionProjectType.EtpSubProject))
- {
- _originalProjectName = ProjectName;
- }
- else
- {
- // This is "normal" project, which in this context means anything non-Venus and non-EtpSubProject.
-
- // If this project has a parent SLN folder, first get the full project name for the SLN folder,
- // and tack on trailing backslash.
- string projectName = String.Empty;
-
- if (ParentProjectGuid != null)
- {
- if (!ParentSolution.ProjectsByGuid.TryGetValue(ParentProjectGuid, out ProjectInSolution parent))
- {
- ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(parent != null, "SubCategoryForSolutionParsingErrors",
- new BuildEventFileInfo(ParentSolution.FullPath), "SolutionParseNestedProjectErrorWithNameAndGuid", ProjectName, ProjectGuid, ParentProjectGuid);
- }
-
- projectName = parent.GetOriginalProjectName() + "\\";
- }
-
- // Now tack on our own project name, and cache it in the ProjectInSolution object for future quick access.
- _originalProjectName = projectName + ProjectName;
- }
- }
-
- return _originalProjectName;
- }
-
- internal string GetProjectGuidWithoutCurlyBrackets()
- {
- if (string.IsNullOrEmpty(ProjectGuid))
- {
- return null;
- }
-
- return ProjectGuid.Trim(new char[] { '{', '}' });
- }
-
- ///
- /// Changes the unique name of the project.
- ///
- internal void UpdateUniqueProjectName(string newUniqueName)
- {
- ErrorUtilities.VerifyThrowArgumentLength(newUniqueName, nameof(newUniqueName));
-
- _uniqueProjectName = newUniqueName;
- }
-
- ///
- /// Cleanse the project name, by replacing characters like '@', '$' with '_'
- ///
- /// The name to be cleansed
- /// string
- private static string CleanseProjectName(string projectName)
- {
- ErrorUtilities.VerifyThrow(projectName != null, "Null strings not allowed.");
-
- // If there are no special chars, just return the original string immediately.
- // Don't even instantiate the StringBuilder.
- int indexOfChar = projectName.IndexOfAny(s_charsToCleanse);
- if (indexOfChar == -1)
- {
- return projectName;
- }
-
- // This is where we're going to work on the final string to return to the caller.
- var cleanProjectName = new StringBuilder(projectName);
-
- // Replace each unclean character with a clean one
- foreach (char uncleanChar in s_charsToCleanse)
- {
- cleanProjectName.Replace(uncleanChar, cleanCharacter);
- }
-
- return cleanProjectName.ToString();
- }
-
- ///
- /// If the unique project name provided collides with one of the standard Solution project
- /// entry point targets (Build, Rebuild, Clean, Publish), then disambiguate it by prepending the string "Solution:"
- ///
- /// The unique name for the project
- /// string
- internal static string DisambiguateProjectTargetName(string uniqueProjectName)
- {
- // Test our unique project name against those names that collide with Solution
- // entry point targets
- foreach (string projectName in projectNamesToDisambiguate)
- {
- if (String.Equals(uniqueProjectName, projectName, StringComparison.OrdinalIgnoreCase))
- {
- // Prepend "Solution:" so that the collision is resolved, but the
- // log of the solution project still looks reasonable.
- return "Solution:" + uniqueProjectName;
- }
- }
-
- return uniqueProjectName;
- }
-
- ///
- /// Check a Project element for known invalid namespace definitions.
- ///
- /// Project XML Element
- /// True if the element contains known invalid namespace definitions
- private static bool ElementContainsInvalidNamespaceDefitions(XmlElement mainProjectElement)
- {
- if (mainProjectElement.HasAttributes)
- {
- // Data warehouse projects (.dwproj) will contain a Project element but are invalid MSBuild. Check attributes
- // on Project for signs that this is a .dwproj file. If there are, it's not a valid MSBuild file.
- return mainProjectElement.Attributes.OfType().Any(a =>
- a.Name.Equals("xmlns:dwd", StringComparison.OrdinalIgnoreCase) ||
- a.Name.StartsWith("xmlns:dd", StringComparison.OrdinalIgnoreCase));
- }
-
- return false;
- }
-
- #endregion
-
- #region Constants
-
- internal const int DependencyLevelUnknown = -1;
- internal const int DependencyLevelBeingDetermined = -2;
-
- #endregion
- }
-}
diff --git a/src/Ionide.ProjInfo.Sln/vendor/SolutionConfigurationInSolution.cs b/src/Ionide.ProjInfo.Sln/vendor/SolutionConfigurationInSolution.cs
deleted file mode 100644
index 42ab8e7d..00000000
--- a/src/Ionide.ProjInfo.Sln/vendor/SolutionConfigurationInSolution.cs
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-namespace Ionide.ProjInfo.Sln.Construction
-{
- ///
- /// This represents an entry for a solution configuration
- ///
- public sealed class SolutionConfigurationInSolution
- {
- ///
- /// Default separator between configuration and platform in configuration
- /// full names
- ///
- internal const char ConfigurationPlatformSeparator = '|';
-
- internal static readonly char[] ConfigurationPlatformSeparatorArray = { '|' };
-
- ///
- /// Constructor
- ///
- internal SolutionConfigurationInSolution(string configurationName, string platformName)
- {
- ConfigurationName = configurationName;
- PlatformName = platformName;
- FullName = ComputeFullName(configurationName, platformName);
- }
-
- ///
- /// The configuration part of this configuration - e.g. "Debug", "Release"
- ///
- public string ConfigurationName { get; }
-
- ///
- /// The platform part of this configuration - e.g. "Any CPU", "Win32"
- ///
- public string PlatformName { get; }
-
- ///
- /// The full name of this configuration - e.g. "Debug|Any CPU"
- ///
- public string FullName { get; }
-
- ///
- /// Given a configuration name and a platform name, compute the full name
- /// of this configuration
- ///
- internal static string ComputeFullName(string configurationName, string platformName)
- {
- // Some configurations don't have the platform part
- if (!string.IsNullOrEmpty(platformName))
- {
- return $"{configurationName}{ConfigurationPlatformSeparator}{platformName}";
- }
- return configurationName;
- }
- }
-}
diff --git a/src/Ionide.ProjInfo.Sln/vendor/SolutionFile.cs b/src/Ionide.ProjInfo.Sln/vendor/SolutionFile.cs
deleted file mode 100644
index cca41c59..00000000
--- a/src/Ionide.ProjInfo.Sln/vendor/SolutionFile.cs
+++ /dev/null
@@ -1,1683 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-using System;
-using System.Collections.Generic;
-using System.Xml;
-using System.IO;
-using System.Text;
-using System.Globalization;
-using System.Runtime.InteropServices;
-using System.Security;
-using System.Text.Json;
-using System.Text.RegularExpressions;
-
-// using ErrorUtilities = Microsoft.Build.Shared.ErrorUtilities;
-// using VisualStudioConstants = Microsoft.Build.Shared.VisualStudioConstants;
-// using ProjectFileErrorUtilities = Microsoft.Build.Shared.ProjectFileErrorUtilities;
-// using BuildEventFileInfo = Microsoft.Build.Shared.BuildEventFileInfo;
-// using ResourceUtilities = Microsoft.Build.Shared.ResourceUtilities;
-// using ExceptionUtilities = Microsoft.Build.Shared.ExceptionHandling;
-// using System.Collections.ObjectModel;
-// using Microsoft.Build.Shared;
-// using Microsoft.Build.Shared.FileSystem;
-using Ionide.ProjInfo.Sln.Shared;
-using System.Collections.ObjectModel;
-
-namespace Ionide.ProjInfo.Sln.Construction
-{
- ///
- /// This class contains the functionality to parse a solution file and return a corresponding
- /// MSBuild project file containing the projects and dependencies defined in the solution.
- ///
- public sealed class SolutionFile
- {
- #region Solution specific constants
-
- // An example of a project line looks like this:
- // Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClassLibrary1", "ClassLibrary1\ClassLibrary1.csproj", "{05A5AD00-71B5-4612-AF2F-9EA9121C4111}"
- private static readonly Lazy s_crackProjectLine = new Lazy(
- () => new Regex
- (
- "^" // Beginning of line
- + "Project\\(\"(?.*)\"\\)"
- + "\\s*=\\s*" // Any amount of whitespace plus "=" plus any amount of whitespace
- + "\"(?.*)\""
- + "\\s*,\\s*" // Any amount of whitespace plus "," plus any amount of whitespace
- + "\"(?.*)\""
- + "\\s*,\\s*" // Any amount of whitespace plus "," plus any amount of whitespace
- + "\"(?.*)\""
- + "$", // End-of-line
- RegexOptions.Compiled
- )
- );
-
- // An example of a property line looks like this:
- // AspNetCompiler.VirtualPath = "/webprecompile"
- // Because website projects now include the target framework moniker as
- // one of their properties, may now have '=' in it.
-
- private static readonly Lazy s_crackPropertyLine = new Lazy(
- () => new Regex
- (
- "^" // Beginning of line
- + "(?[^=]*)"
- + "\\s*=\\s*" // Any amount of whitespace plus "=" plus any amount of whitespace
- + "(?.*)"
- + "$", // End-of-line
- RegexOptions.Compiled
- )
- );
-
- internal const int slnFileMinUpgradableVersion = 7; // Minimum version for MSBuild to give a nice message
- internal const int slnFileMinVersion = 9; // Minimum version for MSBuild to actually do anything useful
- internal const int slnFileMaxVersion = VisualStudioConstants.CurrentVisualStudioSolutionFileVersion;
-
- private const string vbProjectGuid = "{F184B08F-C81C-45F6-A57F-5ABD9991F28F}";
- private const string csProjectGuid = "{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}";
- private const string cpsProjectGuid = "{13B669BE-BB05-4DDF-9536-439F39A36129}";
- private const string cpsCsProjectGuid = "{9A19103F-16F7-4668-BE54-9A1E7A4F7556}";
- private const string cpsVbProjectGuid = "{778DAE3C-4631-46EA-AA77-85C1314464D9}";
- private const string cpsFsProjectGuid = "{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}";
- private const string vjProjectGuid = "{E6FDF86B-F3D1-11D4-8576-0002A516ECE8}";
- private const string vcProjectGuid = "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}";
- private const string fsProjectGuid = "{F2A71F9B-5D33-465A-A702-920D77279786}";
- private const string dbProjectGuid = "{C8D11400-126E-41CD-887F-60BD40844F9E}";
- private const string wdProjectGuid = "{2CFEAB61-6A3B-4EB8-B523-560B4BEEF521}";
- private const string synProjectGuid = "{BBD0F5D1-1CC4-42FD-BA4C-A96779C64378}";
- private const string webProjectGuid = "{E24C65DC-7377-472B-9ABA-BC803B73C61A}";
- private const string solutionFolderGuid = "{2150E333-8FDC-42A3-9474-1A3956D46DE8}";
- private const string sharedProjectGuid = "{D954291E-2A0B-460D-934E-DC6B0785DB48}";
-
- private const char CommentStartChar = '#';
- #endregion
- #region Member data
- private string _solutionFile; // Could be absolute or relative path to the .SLN file.
- private string _solutionFilterFile; // Could be absolute or relative path to the .SLNF file.
- private HashSet _solutionFilter; // The project files to include in loading the solution.
- private bool _parsingForConversionOnly; // Are we parsing this solution to get project reference data during
- // conversion, or in preparation for actually building the solution?
-
- // The list of projects in this SLN, keyed by the project GUID.
- private Dictionary _projects;
-
- // The list of projects in the SLN, in order of their appearance in the SLN.
- private List _projectsInOrder;
-
- // The list of solution configurations in the solution
- private List _solutionConfigurations;
-
- // cached default configuration name for GetDefaultConfigurationName
- private string _defaultConfigurationName;
-
- // cached default platform name for GetDefaultPlatformName
- private string _defaultPlatformName;
-
- // VisualStudionVersion specified in Dev12+ solutions
- private Version _currentVisualStudioVersion;
- private int _currentLineNumber;
-
- // TODO: Unify to NativeMethodsShared.OSUsesCaseSensitive paths
- // when possible.
- private static StringComparer _pathComparer = RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
- ? StringComparer.Ordinal
- : StringComparer.OrdinalIgnoreCase;
-
- #endregion
-
- #region Constructors
-
- ///
- /// Constructor
- ///
- internal SolutionFile()
- {
- }
-
- #endregion
-
- #region Properties
-
- ///
- /// This property returns the list of warnings that were generated during solution parsing
- ///
- internal List SolutionParserWarnings { get; } = new List();
-
- ///
- /// This property returns the list of comments that were generated during the solution parsing
- ///
- internal List SolutionParserComments { get; } = new List();
-
- ///
- /// This property returns the list of error codes for warnings/errors that were generated during solution parsing.
- ///
- internal List SolutionParserErrorCodes { get; } = new List();
-
- ///
- /// Returns the actual major version of the parsed solution file
- ///
- internal int Version { get; private set; }
-
- ///
- /// Returns Visual Studio major version
- ///
- internal int VisualStudioVersion
- {
- get
- {
- if (_currentVisualStudioVersion != null)
- {
- return _currentVisualStudioVersion.Major;
- }
- else
- {
- return Version - 1;
- }
- }
- }
-
- ///
- /// Returns true if the solution contains any web projects
- ///
- internal bool ContainsWebProjects { get; private set; }
-
- ///
- /// Returns true if the solution contains any .wdproj projects. Used to determine
- /// whether we need to load up any projects to examine dependencies.
- ///
- internal bool ContainsWebDeploymentProjects { get; private set; }
-
- ///
- /// All projects in this solution, in the order they appeared in the solution file
- ///
- public IReadOnlyList ProjectsInOrder => _projectsInOrder.AsReadOnly();
-
- ///
- /// The collection of projects in this solution, accessible by their guids as a
- /// string in "{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}" form
- ///
- public IReadOnlyDictionary ProjectsByGuid => new ReadOnlyDictionary(_projects);
-
- ///
- /// This is the read/write accessor for the solution file which we will parse. This
- /// must be set before calling any other methods on this class.
- ///
- ///
- internal string FullPath
- {
- get => _solutionFile;
-
- set
- {
- // Should already be canonicalized to a full path
- ErrorUtilities.VerifyThrowInternalRooted(value);
- // To reduce code duplication, this should be
- // if (FileUtilities.IsSolutionFilterFilename(value))
- // But that's in Microsoft.Build.Framework and this codepath
- // is called from old versions of NuGet that can't resolve
- // Framework (see https://github.com/dotnet/msbuild/issues/5313).
- if (value.EndsWith(".slnf", StringComparison.OrdinalIgnoreCase))
- {
- ParseSolutionFilter(value);
- }
- else
- {
- _solutionFile = value;
- _solutionFilter = null;
-
- SolutionFileDirectory = Path.GetDirectoryName(_solutionFile);
- }
- }
- }
-
- internal string SolutionFileDirectory
- {
- get;
- // This setter is only used by the unit tests
- set;
- }
-
- ///
- /// For unit-testing only.
- ///
- ///
- internal StreamReader SolutionReader { get; set; }
-
- ///
- /// The list of all full solution configurations (configuration + platform) in this solution
- ///
- public IReadOnlyList SolutionConfigurations => _solutionConfigurations.AsReadOnly();
-
- #endregion
-
- #region Methods
-
- internal bool ProjectShouldBuild(string projectFile)
- {
- return _solutionFilter?.Contains(FileUtilities.FixFilePath(projectFile)) != false;
- }
-
- ///
- /// This method takes a path to a solution file, parses the projects and project dependencies
- /// in the solution file, and creates internal data structures representing the projects within
- /// the SLN.
- ///
- public static SolutionFile Parse(string solutionFile)
- {
- var parser = new SolutionFile { FullPath = solutionFile };
- parser.ParseSolutionFile();
- return parser;
- }
-
- ///
- /// Returns "true" if it's a project that's expected to be buildable, or false if it's
- /// not (e.g. a solution folder)
- ///
- /// The project in the solution
- /// Whether the project is expected to be buildable
- internal static bool IsBuildableProject(ProjectInSolution project)
- {
- return project.ProjectType != SolutionProjectType.SolutionFolder && project.ProjectConfigurations.Count > 0;
- }
-
- ///
- /// Given a solution file, parses the header and returns the major version numbers of the solution file
- /// and the visual studio.
- /// Throws InvalidProjectFileException if the solution header is invalid, or if the version is less than
- /// our minimum version.
- ///
- internal static void GetSolutionFileAndVisualStudioMajorVersions(string solutionFile, out int solutionVersion, out int visualStudioMajorVersion)
- {
- ErrorUtilities.VerifyThrow(!String.IsNullOrEmpty(solutionFile), "null solution file passed to GetSolutionFileMajorVersion!");
- ErrorUtilities.VerifyThrowInternalRooted(solutionFile);
-
- const string slnFileHeaderNoVersion = "Microsoft Visual Studio Solution File, Format Version ";
- const string slnFileVSVLinePrefix = "VisualStudioVersion";
- FileStream fileStream = null;
- StreamReader reader = null;
- bool validVersionFound = false;
-
- solutionVersion = 0;
- visualStudioMajorVersion = 0;
-
- try
- {
- // Open the file
- fileStream = File.OpenRead(solutionFile);
- reader = new StreamReader(fileStream, Encoding.GetEncoding(0)); // HIGHCHAR: If solution files have no byte-order marks, then assume ANSI rather than ASCII.
-
- // Read first 4 lines of the solution file.
- // The header is expected to be in line 1 or 2
- // VisualStudioVersion is expected to be in line 3 or 4.
- for (int i = 0; i < 4; i++)
- {
- string line = reader.ReadLine();
-
- if (line == null)
- {
- break;
- }
-
- if (line.Trim().StartsWith(slnFileHeaderNoVersion, StringComparison.Ordinal))
- {
- // Found it. Validate the version.
- string fileVersionFromHeader = line.Substring(slnFileHeaderNoVersion.Length);
-
- if (!System.Version.TryParse(fileVersionFromHeader, out Version version))
- {
- ProjectFileErrorUtilities.ThrowInvalidProjectFile
- (
- new BuildEventFileInfo(solutionFile),
- "SolutionParseVersionMismatchError",
- slnFileMinUpgradableVersion,
- slnFileMaxVersion
- );
- }
-
- solutionVersion = version.Major;
-
- // Validate against our min & max
- ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile
- (
- solutionVersion >= slnFileMinUpgradableVersion,
- "SubCategoryForSolutionParsingErrors",
- new BuildEventFileInfo(solutionFile),
- "SolutionParseVersionMismatchError",
- slnFileMinUpgradableVersion,
- slnFileMaxVersion
- );
-
- validVersionFound = true;
- }
- else if (line.Trim().StartsWith(slnFileVSVLinePrefix, StringComparison.Ordinal))
- {
- Version visualStudioVersion = ParseVisualStudioVersion(line);
- if (visualStudioVersion != null)
- {
- visualStudioMajorVersion = visualStudioVersion.Major;
- }
- }
- }
- }
- finally
- {
- fileStream?.Dispose();
- reader?.Dispose();
- }
-
- if (validVersionFound)
- {
- return;
- }
-
- // Didn't find the header in lines 1-4, so the solution file is invalid.
- ProjectFileErrorUtilities.ThrowInvalidProjectFile
- (
- new BuildEventFileInfo(solutionFile),
- "SolutionParseNoHeaderError"
- );
- }
-
- private void ParseSolutionFilter(string solutionFilterFile)
- {
- _solutionFilterFile = solutionFilterFile;
- try
- {
- _solutionFile = ParseSolutionFromSolutionFilter(solutionFilterFile, out JsonElement solution);
- if (!File.Exists(_solutionFile))
- {
- ProjectFileErrorUtilities.ThrowInvalidProjectFile
- (
- new BuildEventFileInfo(_solutionFile),
- "SolutionFilterMissingSolutionError",
- solutionFilterFile,
- _solutionFile
- );
- }
-
- SolutionFileDirectory = Path.GetDirectoryName(_solutionFile);
-
- _solutionFilter = new HashSet(_pathComparer);
- foreach (JsonElement project in solution.GetProperty("projects").EnumerateArray())
- {
- _solutionFilter.Add(FileUtilities.FixFilePath(project.GetString()));
- }
- }
- catch (Exception e) when (e is JsonException || e is KeyNotFoundException || e is InvalidOperationException)
- {
- ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile
- (
- false, /* Just throw the exception */
- "SubCategoryForSolutionParsingErrors",
- new BuildEventFileInfo(solutionFilterFile),
- e,
- "SolutionFilterJsonParsingError",
- solutionFilterFile
- );
- }
- }
-
- internal static string ParseSolutionFromSolutionFilter(string solutionFilterFile, out JsonElement solution)
- {
- try
- {
- // This is to align MSBuild with what VS permits in loading solution filter files. These are not in them by default but can be added manually.
- JsonDocumentOptions options = new JsonDocumentOptions() { AllowTrailingCommas = true, CommentHandling = JsonCommentHandling.Skip };
- JsonDocument text = JsonDocument.Parse(File.ReadAllText(solutionFilterFile), options);
- solution = text.RootElement.GetProperty("solution");
- return FileUtilities.GetFullPath(solution.GetProperty("path").GetString(), Path.GetDirectoryName(solutionFilterFile));
- }
- catch (Exception e) when (e is JsonException || e is KeyNotFoundException || e is InvalidOperationException)
- {
- ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile
- (
- false, /* Just throw the exception */
- "SubCategoryForSolutionParsingErrors",
- new BuildEventFileInfo(solutionFilterFile),
- e,
- "SolutionFilterJsonParsingError",
- solutionFilterFile
- );
- }
- solution = new JsonElement();
- return string.Empty;
- }
-
- ///
- /// Adds a configuration to this solution
- ///
- internal void AddSolutionConfiguration(string configurationName, string platformName)
- {
- _solutionConfigurations.Add(new SolutionConfigurationInSolution(configurationName, platformName));
- }
-
- ///
- /// Reads a line from the StreamReader, trimming leading and trailing whitespace.
- ///
- ///
- private string ReadLine()
- {
- ErrorUtilities.VerifyThrow(SolutionReader != null, "ParseFileHeader(): reader is null!");
-
- string line = SolutionReader.ReadLine();
- _currentLineNumber++;
-
- return line?.Trim();
- }
-
- ///
- /// This method takes a path to a solution file, parses the projects and project dependencies
- /// in the solution file, and creates internal data structures representing the projects within
- /// the SLN. Used for conversion, which means it allows situations that we refuse to actually build.
- ///
- internal void ParseSolutionFileForConversion()
- {
- _parsingForConversionOnly = true;
- ParseSolutionFile();
- }
-
- ///
- /// This method takes a path to a solution file, parses the projects and project dependencies
- /// in the solution file, and creates internal data structures representing the projects within
- /// the SLN.
- ///
- internal void ParseSolutionFile()
- {
- ErrorUtilities.VerifyThrow(!string.IsNullOrEmpty(_solutionFile), "ParseSolutionFile() got a null solution file!");
- ErrorUtilities.VerifyThrowInternalRooted(_solutionFile);
-
- FileStream fileStream = null;
- SolutionReader = null;
-
- try
- {
- // Open the file
- fileStream = File.OpenRead(_solutionFile);
- SolutionReader = new StreamReader(fileStream, Encoding.GetEncoding(0)); // HIGHCHAR: If solution files have no byte-order marks, then assume ANSI rather than ASCII.
- ParseSolution();
- }
- catch (Exception)
- {
- throw;
- }
- finally
- {
- fileStream?.Dispose();
- SolutionReader?.Dispose();
- }
- }
-
- ///
- /// Parses the SLN file represented by the StreamReader in this.reader, and populates internal
- /// data structures based on the SLN file contents.
- ///
- internal void ParseSolution()
- {
- _projects = new Dictionary(StringComparer.OrdinalIgnoreCase);
- _projectsInOrder = new List();
- ContainsWebProjects = false;
- Version = 0;
- _currentLineNumber = 0;
- _solutionConfigurations = new List();
- _defaultConfigurationName = null;
- _defaultPlatformName = null;
-
- // the raw list of project configurations in solution configurations, to be processed after it's fully read in.
- Dictionary rawProjectConfigurationsEntries = null;
-
- ParseFileHeader();
-
- string str;
- while ((str = ReadLine()) != null)
- {
- if (str.StartsWith("Project(", StringComparison.Ordinal))
- {
- ParseProject(str);
- }
- else if (str.StartsWith("GlobalSection(NestedProjects)", StringComparison.Ordinal))
- {
- ParseNestedProjects();
- }
- else if (str.StartsWith("GlobalSection(SolutionConfigurationPlatforms)", StringComparison.Ordinal))
- {
- ParseSolutionConfigurations();
- }
- else if (str.StartsWith("GlobalSection(ProjectConfigurationPlatforms)", StringComparison.Ordinal))
- {
- rawProjectConfigurationsEntries = ParseProjectConfigurations();
- }
- else if (str.StartsWith("VisualStudioVersion", StringComparison.Ordinal))
- {
- _currentVisualStudioVersion = ParseVisualStudioVersion(str);
- }
- else
- {
- // No other section types to process at this point, so just ignore the line
- // and continue.
- }
- }
-
- if (_solutionFilter != null)
- {
- HashSet projectPaths = new HashSet(_projectsInOrder.Count, _pathComparer);
- foreach (ProjectInSolution project in _projectsInOrder)
- {
- projectPaths.Add(FileUtilities.FixFilePath(project.RelativePath));
- }
- foreach (string project in _solutionFilter)
- {
- if (!projectPaths.Contains(project))
- {
- ProjectFileErrorUtilities.ThrowInvalidProjectFile
- (
- new BuildEventFileInfo(FileUtilities.GetFullPath(project, Path.GetDirectoryName(_solutionFile))),
- "SolutionFilterFilterContainsProjectNotInSolution",
- _solutionFilterFile,
- project,
- _solutionFile
- );
- }
- }
- }
-
- if (rawProjectConfigurationsEntries != null)
- {
- ProcessProjectConfigurationSection(rawProjectConfigurationsEntries);
- }
-
- // Cache the unique name of each project, and check that we don't have any duplicates.
- var projectsByUniqueName = new Dictionary(StringComparer.OrdinalIgnoreCase);
- var projectsByOriginalName = new HashSet(StringComparer.OrdinalIgnoreCase);
-
- foreach (ProjectInSolution proj in _projectsInOrder)
- {
- // Find the unique name for the project. This method also caches the unique name,
- // so it doesn't have to be recomputed later.
- string uniqueName = proj.GetUniqueProjectName();
-
- if (proj.ProjectType == SolutionProjectType.WebProject)
- {
- // Examine port information and determine if we need to disambiguate similarly-named projects with different ports.
- if (Uri.TryCreate(proj.RelativePath, UriKind.Absolute, out Uri uri))
- {
- if (!uri.IsDefaultPort)
- {
- // If there are no other projects with the same name as this one, then we will keep this project's unique name, otherwise
- // we will create a new unique name with the port added.
- foreach (ProjectInSolution otherProj in _projectsInOrder)
- {
- if (ReferenceEquals(proj, otherProj))
- {
- continue;
- }
-
- if (String.Equals(otherProj.ProjectName, proj.ProjectName, StringComparison.OrdinalIgnoreCase))
- {
- uniqueName = $"{uniqueName}:{uri.Port}";
- proj.UpdateUniqueProjectName(uniqueName);
- break;
- }
- }
- }
- }
- }
-
- // Detect collision caused by unique name's normalization
- if (projectsByUniqueName.TryGetValue(uniqueName, out ProjectInSolution project))
- {
- // Did normalization occur in the current project?
- if (uniqueName != proj.ProjectName)
- {
- // Generates a new unique name
- string tempUniqueName = $"{uniqueName}_{proj.GetProjectGuidWithoutCurlyBrackets()}";
- proj.UpdateUniqueProjectName(tempUniqueName);
- uniqueName = tempUniqueName;
- }
- // Did normalization occur in a previous project?
- else if (uniqueName != project.ProjectName)
- {
- // Generates a new unique name
- string tempUniqueName = $"{uniqueName}_{project.GetProjectGuidWithoutCurlyBrackets()}";
- project.UpdateUniqueProjectName(tempUniqueName);
-
- projectsByUniqueName.Remove(uniqueName);
- projectsByUniqueName.Add(tempUniqueName, project);
- }
- }
-
- bool uniqueNameExists = projectsByUniqueName.ContainsKey(uniqueName);
-
- // Add the unique name (if it does not exist) to the hash table
- if (!uniqueNameExists)
- {
- projectsByUniqueName.Add(uniqueName, proj);
- }
-
- bool didntAlreadyExist = !uniqueNameExists && projectsByOriginalName.Add(proj.GetOriginalProjectName());
-
- ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(
- didntAlreadyExist,
- "SubCategoryForSolutionParsingErrors",
- new BuildEventFileInfo(FullPath),
- "SolutionParseDuplicateProject",
- uniqueNameExists ? uniqueName : proj.ProjectName);
- }
- } // ParseSolutionFile()
-
- ///
- /// This method searches the first two lines of the solution file opened by the specified
- /// StreamReader for the solution file header. An exception is thrown if it is not found.
- ///
- /// The solution file header looks like this:
- ///
- /// Microsoft Visual Studio Solution File, Format Version 9.00
- ///
- ///
- private void ParseFileHeader()
- {
- ErrorUtilities.VerifyThrow(SolutionReader != null, "ParseFileHeader(): reader is null!");
-
- const string slnFileHeaderNoVersion = "Microsoft Visual Studio Solution File, Format Version ";
-
- // Read the file header. This can be on either of the first two lines.
- for (int i = 1; i <= 2; i++)
- {
- string str = ReadLine();
- if (str == null)
- {
- break;
- }
-
- if (str.StartsWith(slnFileHeaderNoVersion, StringComparison.Ordinal))
- {
- // Found it. Validate the version.
- ValidateSolutionFileVersion(str.Substring(slnFileHeaderNoVersion.Length));
- return;
- }
- }
-
- // Didn't find the header on either the first or second line, so the solution file
- // is invalid.
- ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(false, "SubCategoryForSolutionParsingErrors",
- new BuildEventFileInfo(FullPath), "SolutionParseNoHeaderError");
- }
-
- ///
- /// This method parses the Visual Studio version in Dev 12 solution files
- /// The version line looks like this:
- ///
- /// VisualStudioVersion = 12.0.20311.0 VSPRO_PLATFORM
- ///
- /// If such a line is found, the version is stored in this.currentVisualStudioVersion
- ///
- private static Version ParseVisualStudioVersion(string str)
- {
- Version currentVisualStudioVersion = null;
- char[] delimiterChars = { ' ', '=' };
- string[] words = str.Split(delimiterChars, StringSplitOptions.RemoveEmptyEntries);
-
- if (words.Length >= 2)
- {
- string versionStr = words[1];
- if (!System.Version.TryParse(versionStr, out currentVisualStudioVersion))
- {
- currentVisualStudioVersion = null;
- }
- }
-
- return currentVisualStudioVersion;
- }
- ///
- /// This method extracts the whole part of the version number from the specified line
- /// containing the solution file format header, and throws an exception if the version number
- /// is outside of the valid range.
- ///
- /// The solution file header looks like this:
- ///
- /// Microsoft Visual Studio Solution File, Format Version 9.00
- ///
- ///
- ///
- private void ValidateSolutionFileVersion(string versionString)
- {
- ErrorUtilities.VerifyThrow(versionString != null, "ValidateSolutionFileVersion() got a null line!");
-
- if (!System.Version.TryParse(versionString, out Version version))
- {
- ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(false, "SubCategoryForSolutionParsingErrors",
- new BuildEventFileInfo(FullPath, _currentLineNumber, 0), "SolutionParseVersionMismatchError",
- slnFileMinUpgradableVersion, slnFileMaxVersion);
- }
-
- Version = version.Major;
-
- // Validate against our min & max
- ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(
- Version >= slnFileMinUpgradableVersion,
- "SubCategoryForSolutionParsingErrors",
- new BuildEventFileInfo(FullPath, _currentLineNumber, 0),
- "SolutionParseVersionMismatchError",
- slnFileMinUpgradableVersion, slnFileMaxVersion);
- // If the solution file version is greater than the maximum one we will create a comment rather than warn
- // as users such as blend opening a dev10 project cannot do anything about it.
- if (Version > slnFileMaxVersion)
- {
- SolutionParserComments.Add(ResourceUtilities.FormatResourceString("UnrecognizedSolutionComment", Version));
- }
- }
-
- ///
- ///
- /// This method processes a "Project" section in the solution file opened by the specified
- /// StreamReader, and returns a populated ProjectInSolution instance, if successful.
- /// An exception is thrown if the solution file is invalid.
- ///
- /// The format of the parts of a Project section that we care about is as follows:
- ///
- /// Project("{Project type GUID}") = "Project name", "Relative path to project file", "{Project GUID}"
- /// ProjectSection(ProjectDependencies) = postProject
- /// {Parent project unique name} = {Parent project unique name}
- /// ...
- /// EndProjectSection
- /// EndProject
- ///
- ///
- private void ParseProject(string firstLine)
- {
- ErrorUtilities.VerifyThrow(!string.IsNullOrEmpty(firstLine), "ParseProject() got a null firstLine!");
- ErrorUtilities.VerifyThrow(SolutionReader != null, "ParseProject() got a null reader!");
-
- var proj = new ProjectInSolution(this);
-
- // Extract the important information from the first line.
- ParseFirstProjectLine(firstLine, proj);
-
- // Search for project dependencies. Keeping reading lines until we either 1.) reach
- // the end of the file, 2.) see "ProjectSection(ProjectDependencies)" at the beginning
- // of the line, or 3.) see "EndProject" at the beginning of the line.
- string line;
- while ((line = ReadLine()) != null)
- {
- // If we see an "EndProject", well ... that's the end of this project!
- if (line == "EndProject")
- {
- break;
- }
- else if (line.StartsWith("ProjectSection(ProjectDependencies)", StringComparison.Ordinal))
- {
- // We have a ProjectDependencies section. Each subsequent line should identify
- // a dependency.
- line = ReadLine();
- while ((line?.StartsWith("EndProjectSection", StringComparison.Ordinal) == false))
- {
- // This should be a dependency. The GUID identifying the parent project should
- // be both the property name and the property value.
- Match match = s_crackPropertyLine.Value.Match(line);
- ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(match.Success, "SubCategoryForSolutionParsingErrors",
- new BuildEventFileInfo(FullPath, _currentLineNumber, 0), "SolutionParseProjectDepGuidError", proj.ProjectName);
-
- string referenceGuid = match.Groups["PROPERTYNAME"].Value.Trim();
- proj.AddDependency(referenceGuid);
-
- line = ReadLine();
- }
- }
- else if (line.StartsWith("ProjectSection(WebsiteProperties)", StringComparison.Ordinal))
- {
- // We have a WebsiteProperties section. This section is present only in Venus
- // projects, and contains properties that we'll need in order to call the
- // AspNetCompiler task.
- line = ReadLine();
- while ((line?.StartsWith("EndProjectSection", StringComparison.Ordinal) == false))
- {
- Match match = s_crackPropertyLine.Value.Match(line);
- ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(match.Success, "SubCategoryForSolutionParsingErrors",
- new BuildEventFileInfo(FullPath, _currentLineNumber, 0), "SolutionParseWebProjectPropertiesError", proj.ProjectName);
-
- string propertyName = match.Groups["PROPERTYNAME"].Value.Trim();
- string propertyValue = match.Groups["PROPERTYVALUE"].Value.Trim();
-
- ParseAspNetCompilerProperty(proj, propertyName, propertyValue);
-
- line = ReadLine();
- }
- }
- else if (line.StartsWith("ProjectSection(SolutionItems)", StringComparison.Ordinal))
- {
- // We have a SolutionItems section. Each subsequent line should identify
- // a solution item.
- line = ReadLine();
- while ((line != null) && (!line.StartsWith("EndProjectSection", StringComparison.Ordinal)))
- {
- proj.ProjectType = SolutionProjectType.SolutionFolder;
-
- // This should be a dependency. The GUID identifying the parent project should
- // be both the property name and the property value.
- Match match = s_crackPropertyLine.Value.Match(line);
- if (match.Success)
- {
- string relativeFilePath = match.Groups["PROPERTYNAME"].Value.Trim();
- proj.AddFolderFile(relativeFilePath);
- }
-
- line = ReadLine();
- }
- }
- else if (line.StartsWith("Project(", StringComparison.Ordinal))
- {
- // Another Project spotted instead of EndProject for the current one - solution file is malformed
- string warning = ResourceUtilities.FormatResourceString(out _, out _, "Shared.InvalidProjectFile",
- _solutionFile, proj.ProjectName);
- SolutionParserWarnings.Add(warning);
-
- // The line with new project is already read and we can't go one line back - we have no choice but to recursively parse spotted project
- ParseProject(line);
-
- // We're not waiting for the EndProject for malformed project, so we carry on
- break;
- }
- }
-
- ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(line != null, "SubCategoryForSolutionParsingErrors",
- new BuildEventFileInfo(FullPath), "SolutionParseProjectEofError", proj.ProjectName);
-
- // Add the project to the collection
- AddProjectToSolution(proj);
- // If the project is an etp project then parse the etp project file
- // to get the projects contained in it.
- if (IsEtpProjectFile(proj.RelativePath))
- {
- ParseEtpProject(proj);
- }
- } // ParseProject()
-
- ///
- /// This method will parse a .etp project recursively and
- /// add all the projects found to projects and projectsInOrder
- ///
- /// ETP Project
- internal void ParseEtpProject(ProjectInSolution etpProj)
- {
- var etpProjectDocument = new XmlDocument();
- // Get the full path to the .etp project file
- string fullPathToEtpProj = Path.Combine(SolutionFileDirectory, etpProj.RelativePath);
- string etpProjectRelativeDir = Path.GetDirectoryName(etpProj.RelativePath);
- try
- {
- /****************************************************************************
- * A Typical .etp project file will look like this
- *
- *
- *
- * Microsoft Visual Studio Application Template File
- * 1.00
- *
- *
- * ClassLibrary2\ClassLibrary2.csproj
- *
- *
- *
- *
- * ClassLibrary2\ClassLibrary2.csproj
- * {73D0F4CE-D9D3-4E8B-81E4-B26FBF4CC2FE}
- *
- *
- *
- *
- **********************************************************************************/
- // Make sure the XML reader ignores DTD processing
- var readerSettings = new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore };
-
- // Load the .etp project file thru the XML reader
- using (XmlReader xmlReader = XmlReader.Create(fullPathToEtpProj, readerSettings))
- {
- etpProjectDocument.Load(xmlReader);
- }
-
- // We need to parse the .etp project file to get the names of projects contained
- // in the .etp Project. The projects are listed under /EFPROJECT/GENERAL/References/Reference node in the .etp project file.
- // The /EFPROJECT/GENERAL/Views/ProjectExplorer node will not necessarily contain
- // all the projects in the .etp project. Therefore, we need to look at
- // /EFPROJECT/GENERAL/References/Reference.
- // Find the /EFPROJECT/GENERAL/References/Reference node
- // Note that this is case sensitive
- XmlNodeList referenceNodes = etpProjectDocument.DocumentElement.SelectNodes("/EFPROJECT/GENERAL/References/Reference");
- // Do the right thing for each element
- foreach (XmlNode referenceNode in referenceNodes)
- {
- // Get the relative path to the project file
- string fileElementValue = referenceNode.SelectSingleNode("FILE").InnerText;
- // If element is not present under then we don't do anything.
- if (fileElementValue != null)
- {
- // Create and populate a ProjectInSolution for the project
- var proj = new ProjectInSolution(this)
- {
- RelativePath = Path.Combine(etpProjectRelativeDir, fileElementValue)
- };
-
- // Verify the relative path specified in the .etp proj file
- ValidateProjectRelativePath(proj);
- proj.ProjectType = SolutionProjectType.EtpSubProject;
- proj.ProjectName = proj.RelativePath;
- XmlNode projGuidNode = referenceNode.SelectSingleNode("GUIDPROJECTID");
-
- // It is ok for a project to not have a guid inside an etp project.
- // If a solution file contains a project without a guid it fails to
- // load in Everett. But if an etp project contains a project without
- // a guid it loads well in Everett and p2p references to/from this project
- // are preserved. So we should make sure that we don’t error in this
- // situation while upgrading.
- proj.ProjectGuid = projGuidNode?.InnerText ?? String.Empty;
-
- // Add the recently created proj to the collection of projects
- AddProjectToSolution(proj);
- // If the project is an etp project recurse
- if (IsEtpProjectFile(fileElementValue))
- {
- ParseEtpProject(proj);
- }
- }
- }
- }
- // catch all sorts of exceptions - if we encounter any problems here, we just assume the .etp project file is not in the correct format
-
- // handle security errors
- catch (SecurityException e)
- {
- // Log a warning
- string warning = ResourceUtilities.FormatResourceString(out string errorCode, out _, "Shared.ProjectFileCouldNotBeLoaded",
- etpProj.RelativePath, e.Message);
- SolutionParserWarnings.Add(warning);
- SolutionParserErrorCodes.Add(errorCode);
- }
- // handle errors in path resolution
- catch (NotSupportedException e)
- {
- // Log a warning
- string warning = ResourceUtilities.FormatResourceString(out string errorCode, out _, "Shared.ProjectFileCouldNotBeLoaded",
- etpProj.RelativePath, e.Message);
- SolutionParserWarnings.Add(warning);
- SolutionParserErrorCodes.Add(errorCode);
- }
- // handle errors in loading project file
- catch (IOException e)
- {
- // Log a warning
- string warning = ResourceUtilities.FormatResourceString(out string errorCode, out _, "Shared.ProjectFileCouldNotBeLoaded",
- etpProj.RelativePath, e.Message);
- SolutionParserWarnings.Add(warning);
- SolutionParserErrorCodes.Add(errorCode);
- }
- // handle errors in loading project file
- catch (UnauthorizedAccessException e)
- {
- // Log a warning
- string warning = ResourceUtilities.FormatResourceString(out string errorCode, out _, "Shared.ProjectFileCouldNotBeLoaded",
- etpProj.RelativePath, e.Message);
- SolutionParserWarnings.Add(warning);
- SolutionParserErrorCodes.Add(errorCode);
- }
- // handle XML parsing errors
- catch (XmlException e)
- {
- // Log a warning
- string warning = ResourceUtilities.FormatResourceString(out string errorCode, out _, "Shared.InvalidProjectFile",
- etpProj.RelativePath, e.Message);
- SolutionParserWarnings.Add(warning);
- SolutionParserErrorCodes.Add(errorCode);
- }
- }
-
- ///
- /// Adds a given project to the project collections of this class
- ///
- /// proj
- private void AddProjectToSolution(ProjectInSolution proj)
- {
- if (!String.IsNullOrEmpty(proj.ProjectGuid))
- {
- _projects[proj.ProjectGuid] = proj;
- }
- _projectsInOrder.Add(proj);
- }
-
- ///
- /// Checks whether a given project has a .etp extension.
- ///
- ///
- private static bool IsEtpProjectFile(string projectFile)
- {
- return projectFile.EndsWith(".etp", StringComparison.OrdinalIgnoreCase);
- }
-
- ///
- /// Validate relative path of a project
- ///
- /// proj
- private void ValidateProjectRelativePath(ProjectInSolution proj)
- {
- // Verify the relative path is not null
- ErrorUtilities.VerifyThrow(proj.RelativePath != null, "Project relative path cannot be null.");
-
- // Verify the relative path does not contain invalid characters
- ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(proj.RelativePath.IndexOfAny(Path.GetInvalidPathChars()) == -1,
- "SubCategoryForSolutionParsingErrors",
- new BuildEventFileInfo(FullPath, _currentLineNumber, 0),
- "SolutionParseInvalidProjectFileNameCharacters",
- proj.ProjectName, proj.RelativePath);
-
- // Verify the relative path is not empty string
- ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(proj.RelativePath.Length > 0,
- "SubCategoryForSolutionParsingErrors",
- new BuildEventFileInfo(FullPath, _currentLineNumber, 0),
- "SolutionParseInvalidProjectFileNameEmpty",
- proj.ProjectName);
- }
-
- ///
- /// Takes a property name / value that comes from the SLN file for a Venus project, and
- /// stores it appropriately in our data structures.
- ///
- private static void ParseAspNetCompilerProperty
- (
- ProjectInSolution proj,
- string propertyName,
- string propertyValue
- )
- {
- // What we expect to find in the SLN file is something that looks like this:
- //
- // Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "c:\...\myfirstwebsite\", "..\..\..\..\..\..\rajeev\temp\websites\myfirstwebsite", "{956CC04E-FD59-49A9-9099-96888CB6F366}"
- // ProjectSection(WebsiteProperties) = preProject
- // TargetFrameworkMoniker = ".NETFramework,Version%3Dv4.0"
- // ProjectReferences = "{FD705688-88D1-4C22-9BFF-86235D89C2FC}|CSClassLibrary1.dll;{F0726D09-042B-4A7A-8A01-6BED2422BD5D}|VCClassLibrary1.dll;"
- // Debug.AspNetCompiler.VirtualPath = "/publishfirst"
- // Debug.AspNetCompiler.PhysicalPath = "..\..\..\..\..\..\rajeev\temp\websites\myfirstwebsite\"
- // Debug.AspNetCompiler.TargetPath = "..\..\..\..\..\..\rajeev\temp\publishfirst\"
- // Debug.AspNetCompiler.ForceOverwrite = "true"
- // Debug.AspNetCompiler.Updateable = "true"
- // Debug.AspNetCompiler.Enabled = "true"
- // Debug.AspNetCompiler.Debug = "true"
- // Debug.AspNetCompiler.KeyFile = ""
- // Debug.AspNetCompiler.KeyContainer = ""
- // Debug.AspNetCompiler.DelaySign = "true"
- // Debug.AspNetCompiler.AllowPartiallyTrustedCallers = "true"
- // Debug.AspNetCompiler.FixedNames = "true"
- // Release.AspNetCompiler.VirtualPath = "/publishfirst"
- // Release.AspNetCompiler.PhysicalPath = "..\..\..\..\..\..\rajeev\temp\websites\myfirstwebsite\"
- // Release.AspNetCompiler.TargetPath = "..\..\..\..\..\..\rajeev\temp\publishfirst\"
- // Release.AspNetCompiler.ForceOverwrite = "true"
- // Release.AspNetCompiler.Updateable = "true"
- // Release.AspNetCompiler.Enabled = "true"
- // Release.AspNetCompiler.Debug = "false"
- // Release.AspNetCompiler.KeyFile = ""
- // Release.AspNetCompiler.KeyContainer = ""
- // Release.AspNetCompiler.DelaySign = "true"
- // Release.AspNetCompiler.AllowPartiallyTrustedCallers = "true"
- // Release.AspNetCompiler.FixedNames = "true"
- // EndProjectSection
- // EndProject
- //
- // This method is responsible for parsing each of the lines within the "WebsiteProperties" section.
- // The first component of each property name is actually the configuration for which that
- // property applies.
-
- int indexOfFirstDot = propertyName.IndexOf('.');
- if (indexOfFirstDot != -1)
- {
- // The portion before the first dot is the configuration name.
- string configurationName = propertyName.Substring(0, indexOfFirstDot);
-
- // The rest of it is the actual property name.
- string aspNetPropertyName = ((propertyName.Length - indexOfFirstDot) > 0) ? propertyName.Substring(indexOfFirstDot + 1, propertyName.Length - indexOfFirstDot - 1) : "";
-
- // And the part after the sign is the property value (which was parsed out for us prior
- // to calling this method).
- propertyValue = TrimQuotes(propertyValue);
-
- // Grab the parameters for this specific configuration if they exist.
- object aspNetCompilerParametersObject = proj.AspNetConfigurations[configurationName];
- AspNetCompilerParameters aspNetCompilerParameters;
-
- if (aspNetCompilerParametersObject == null)
- {
- // If it didn't exist, create a new one.
- aspNetCompilerParameters = new AspNetCompilerParameters
- {
- aspNetVirtualPath = String.Empty,
- aspNetPhysicalPath = String.Empty,
- aspNetTargetPath = String.Empty,
- aspNetForce = String.Empty,
- aspNetUpdateable = String.Empty,
- aspNetDebug = String.Empty,
- aspNetKeyFile = String.Empty,
- aspNetKeyContainer = String.Empty,
- aspNetDelaySign = String.Empty,
- aspNetAPTCA = String.Empty,
- aspNetFixedNames = String.Empty
- };
- }
- else
- {
- // Otherwise just unbox it.
- aspNetCompilerParameters = (AspNetCompilerParameters)aspNetCompilerParametersObject;
- }
-
- // Update the appropriate field within the parameters struct.
- if (aspNetPropertyName == "AspNetCompiler.VirtualPath")
- {
- aspNetCompilerParameters.aspNetVirtualPath = propertyValue;
- }
- else if (aspNetPropertyName == "AspNetCompiler.PhysicalPath")
- {
- aspNetCompilerParameters.aspNetPhysicalPath = propertyValue;
- }
- else if (aspNetPropertyName == "AspNetCompiler.TargetPath")
- {
- aspNetCompilerParameters.aspNetTargetPath = propertyValue;
- }
- else if (aspNetPropertyName == "AspNetCompiler.ForceOverwrite")
- {
- aspNetCompilerParameters.aspNetForce = propertyValue;
- }
- else if (aspNetPropertyName == "AspNetCompiler.Updateable")
- {
- aspNetCompilerParameters.aspNetUpdateable = propertyValue;
- }
- else if (aspNetPropertyName == "AspNetCompiler.Debug")
- {
- aspNetCompilerParameters.aspNetDebug = propertyValue;
- }
- else if (aspNetPropertyName == "AspNetCompiler.KeyFile")
- {
- aspNetCompilerParameters.aspNetKeyFile = propertyValue;
- }
- else if (aspNetPropertyName == "AspNetCompiler.KeyContainer")
- {
- aspNetCompilerParameters.aspNetKeyContainer = propertyValue;
- }
- else if (aspNetPropertyName == "AspNetCompiler.DelaySign")
- {
- aspNetCompilerParameters.aspNetDelaySign = propertyValue;
- }
- else if (aspNetPropertyName == "AspNetCompiler.AllowPartiallyTrustedCallers")
- {
- aspNetCompilerParameters.aspNetAPTCA = propertyValue;
- }
- else if (aspNetPropertyName == "AspNetCompiler.FixedNames")
- {
- aspNetCompilerParameters.aspNetFixedNames = propertyValue;
- }
-
- // Store the updated parameters struct back into the hashtable by configuration name.
- proj.AspNetConfigurations[configurationName] = aspNetCompilerParameters;
- }
- else
- {
- // ProjectReferences = "{FD705688-88D1-4C22-9BFF-86235D89C2FC}|CSClassLibrary1.dll;{F0726D09-042B-4A7A-8A01-6BED2422BD5D}|VCClassLibrary1.dll;"
- if (string.Equals(propertyName, "ProjectReferences", StringComparison.OrdinalIgnoreCase))
- {
- string[] projectReferenceEntries = propertyValue.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
-
- foreach (string projectReferenceEntry in projectReferenceEntries)
- {
- int indexOfBar = projectReferenceEntry.IndexOf('|');
-
- // indexOfBar could be -1 if we had semicolons in the file names, so skip entries that
- // don't contain a guid. File names may not contain the '|' character
- if (indexOfBar != -1)
- {
- int indexOfOpeningBrace = projectReferenceEntry.IndexOf('{');
- if (indexOfOpeningBrace != -1)
- {
- int indexOfClosingBrace = projectReferenceEntry.IndexOf('}', indexOfOpeningBrace);
- if (indexOfClosingBrace != -1)
- {
- string referencedProjectGuid = projectReferenceEntry.Substring(indexOfOpeningBrace,
- indexOfClosingBrace - indexOfOpeningBrace + 1);
-
- proj.AddDependency(referencedProjectGuid);
- proj.ProjectReferences.Add(referencedProjectGuid);
- }
- }
- }
- }
- }
- else if (String.Equals(propertyName, "TargetFrameworkMoniker", StringComparison.OrdinalIgnoreCase))
- {
- //Website project need to back support 3.5 msbuild parser for the Blend (it is not move to .Net4.0 yet.)
- //However, 3.5 version of Solution parser can't handle a equal sign in the value.
- //The "=" in targetframeworkMoniker was escaped to "%3D" for Orcas
- string targetFrameworkMoniker = TrimQuotes(propertyValue);
- proj.TargetFrameworkMoniker = Shared.EscapingUtilities.UnescapeAll(targetFrameworkMoniker);
- }
- }
- }
-
- ///
- /// Strips a single pair of leading/trailing double-quotes from a string.
- ///
- private static string TrimQuotes
- (
- string property
- )
- {
- // If the incoming string starts and ends with a double-quote, strip the double-quotes.
- if (!string.IsNullOrEmpty(property) && (property[0] == '"') && (property[property.Length - 1] == '"'))
- {
- return property.Substring(1, property.Length - 2);
- }
- else
- {
- return property;
- }
- }
-
- ///
- /// Parse the first line of a Project section of a solution file. This line should look like:
- ///
- /// Project("{Project type GUID}") = "Project name", "Relative path to project file", "{Project GUID}"
- ///
- ///
- ///
- ///
- internal void ParseFirstProjectLine
- (
- string firstLine,
- ProjectInSolution proj
- )
- {
- Match match = s_crackProjectLine.Value.Match(firstLine);
- ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(match.Success, "SubCategoryForSolutionParsingErrors",
- new BuildEventFileInfo(FullPath, _currentLineNumber, 0), "SolutionParseProjectError");
-
- string projectTypeGuid = match.Groups["PROJECTTYPEGUID"].Value.Trim();
- proj.ProjectName = match.Groups["PROJECTNAME"].Value.Trim();
- proj.RelativePath = match.Groups["RELATIVEPATH"].Value.Trim();
- proj.ProjectGuid = match.Groups["PROJECTGUID"].Value.Trim();
-
- // If the project name is empty (as in some bad solutions) set it to some generated generic value.
- // This allows us to at least generate reasonable target names etc. instead of crashing.
- if (String.IsNullOrEmpty(proj.ProjectName))
- {
- proj.ProjectName = "EmptyProjectName." + Guid.NewGuid();
- }
-
- // Validate project relative path
- ValidateProjectRelativePath(proj);
-
- // Figure out what type of project this is.
- if ((String.Equals(projectTypeGuid, vbProjectGuid, StringComparison.OrdinalIgnoreCase)) ||
- (String.Equals(projectTypeGuid, csProjectGuid, StringComparison.OrdinalIgnoreCase)) ||
- (String.Equals(projectTypeGuid, cpsProjectGuid, StringComparison.OrdinalIgnoreCase)) ||
- (String.Equals(projectTypeGuid, cpsCsProjectGuid, StringComparison.OrdinalIgnoreCase)) ||
- (String.Equals(projectTypeGuid, cpsVbProjectGuid, StringComparison.OrdinalIgnoreCase)) ||
- (String.Equals(projectTypeGuid, cpsFsProjectGuid, StringComparison.OrdinalIgnoreCase)) ||
- (String.Equals(projectTypeGuid, fsProjectGuid, StringComparison.OrdinalIgnoreCase)) ||
- (String.Equals(projectTypeGuid, dbProjectGuid, StringComparison.OrdinalIgnoreCase)) ||
- (String.Equals(projectTypeGuid, vjProjectGuid, StringComparison.OrdinalIgnoreCase)) ||
- (String.Equals(projectTypeGuid, synProjectGuid, StringComparison.OrdinalIgnoreCase)))
- {
- proj.ProjectType = SolutionProjectType.KnownToBeMSBuildFormat;
- }
- else if (String.Equals(projectTypeGuid, sharedProjectGuid, StringComparison.OrdinalIgnoreCase))
- {
- proj.ProjectType = SolutionProjectType.SharedProject;
- }
- else if (String.Equals(projectTypeGuid, solutionFolderGuid, StringComparison.OrdinalIgnoreCase))
- {
- proj.ProjectType = SolutionProjectType.SolutionFolder;
- }
- // MSBuild format VC projects have the same project type guid as old style VC projects.
- // If it's not an old-style VC project, we'll assume it's MSBuild format
- else if (String.Equals(projectTypeGuid, vcProjectGuid, StringComparison.OrdinalIgnoreCase))
- {
- if (String.Equals(proj.Extension, ".vcproj", StringComparison.OrdinalIgnoreCase))
- {
- if (!_parsingForConversionOnly)
- {
- ProjectFileErrorUtilities.ThrowInvalidProjectFile(new BuildEventFileInfo(FullPath), "ProjectUpgradeNeededToVcxProj", proj.RelativePath);
- }
- // otherwise, we're parsing this solution file because we want the P2P information during
- // conversion, and it's perfectly valid for an unconverted solution file to still contain .vcprojs
- }
- else
- {
- proj.ProjectType = SolutionProjectType.KnownToBeMSBuildFormat;
- }
- }
- else if (String.Equals(projectTypeGuid, webProjectGuid, StringComparison.OrdinalIgnoreCase))
- {
- proj.ProjectType = SolutionProjectType.WebProject;
- ContainsWebProjects = true;
- }
- else if (String.Equals(projectTypeGuid, wdProjectGuid, StringComparison.OrdinalIgnoreCase))
- {
- proj.ProjectType = SolutionProjectType.WebDeploymentProject;
- ContainsWebDeploymentProjects = true;
- }
- else
- {
- proj.ProjectType = SolutionProjectType.Unknown;
- }
- }
-
- ///
- /// Read nested projects section.
- /// This is required to find a unique name for each project's target
- ///
- internal void ParseNestedProjects()
- {
- do
- {
- string str = ReadLine();
- if ((str == null) || (str == "EndGlobalSection"))
- {
- break;
- }
-
- // Ignore empty line or comment
- if (String.IsNullOrWhiteSpace(str) || str[0] == CommentStartChar)
- {
- continue;
- }
-
- Match match = s_crackPropertyLine.Value.Match(str);
- ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(match.Success, "SubCategoryForSolutionParsingErrors",
- new BuildEventFileInfo(FullPath, _currentLineNumber, 0), "SolutionParseNestedProjectError");
-
- string projectGuid = match.Groups["PROPERTYNAME"].Value.Trim();
- string parentProjectGuid = match.Groups["PROPERTYVALUE"].Value.Trim();
-
- if (!_projects.TryGetValue(projectGuid, out ProjectInSolution proj))
- {
- ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(proj != null, "SubCategoryForSolutionParsingErrors",
- new BuildEventFileInfo(FullPath, _currentLineNumber, 0), "SolutionParseNestedProjectUndefinedError", projectGuid, parentProjectGuid);
- }
-
- proj.ParentProjectGuid = parentProjectGuid;
- } while (true);
- }
-
- ///
- /// Read solution configuration section.
- ///
- ///
- /// A sample section:
- ///
- /// GlobalSection(SolutionConfigurationPlatforms) = preSolution
- /// Debug|Any CPU = Debug|Any CPU
- /// Release|Any CPU = Release|Any CPU
- /// EndGlobalSection
- ///
- internal void ParseSolutionConfigurations()
- {
- var nameValueSeparators = '=';
-
- do
- {
- string str = ReadLine();
-
- if ((str == null) || (str == "EndGlobalSection"))
- {
- break;
- }
-
- // Ignore empty line or comment
- if (String.IsNullOrWhiteSpace(str) || str[0] == CommentStartChar)
- {
- continue;
- }
-
- string[] configurationNames = str.Split(nameValueSeparators);
-
- // There should be exactly one '=' character, separating two names.
- ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(configurationNames.Length == 2, "SubCategoryForSolutionParsingErrors",
- new BuildEventFileInfo(FullPath, _currentLineNumber, 0), "SolutionParseInvalidSolutionConfigurationEntry", str);
-
- string fullConfigurationName = configurationNames[0].Trim();
-
- //Fixing bug 555577: Solution file can have description information, in which case we ignore.
- if (String.Equals(fullConfigurationName, "DESCRIPTION", StringComparison.OrdinalIgnoreCase))
- {
- continue;
- }
-
- // Both names must be identical
- ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(fullConfigurationName == configurationNames[1].Trim(), "SubCategoryForSolutionParsingErrors",
- new BuildEventFileInfo(FullPath, _currentLineNumber, 0), "SolutionParseInvalidSolutionConfigurationEntry", str);
-
- var (configuration, platform) = ParseConfigurationName(fullConfigurationName, FullPath, _currentLineNumber, str);
-
- _solutionConfigurations.Add(new SolutionConfigurationInSolution(configuration, platform));
- } while (true);
- }
-
- internal static (string Configuration, string Platform) ParseConfigurationName(string fullConfigurationName, string projectPath, int lineNumber, string containingString)
- {
- string[] configurationPlatformParts = fullConfigurationName.Split(SolutionConfigurationInSolution.ConfigurationPlatformSeparatorArray);
-
- ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(
- configurationPlatformParts.Length == 2,
- "SubCategoryForSolutionParsingErrors",
- new BuildEventFileInfo(projectPath, lineNumber, 0),
- "SolutionParseInvalidSolutionConfigurationEntry",
- containingString);
-
- return (configurationPlatformParts[0], configurationPlatformParts[1]);
- }
-
- ///
- /// Read project configurations in solution configurations section.
- ///
- ///
- /// A sample (incomplete) section:
- ///
- /// GlobalSection(ProjectConfigurationPlatforms) = postSolution
- /// {6185CC21-BE89-448A-B3C0-D1C27112E595}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- /// {6185CC21-BE89-448A-B3C0-D1C27112E595}.Debug|Any CPU.Build.0 = Debug|Any CPU
- /// {6185CC21-BE89-448A-B3C0-D1C27112E595}.Debug|Mixed Platforms.ActiveCfg = Release|Any CPU
- /// {6185CC21-BE89-448A-B3C0-D1C27112E595}.Debug|Mixed Platforms.Build.0 = Release|Any CPU
- /// {6185CC21-BE89-448A-B3C0-D1C27112E595}.Debug|Win32.ActiveCfg = Debug|Any CPU
- /// {A6F99D27-47B9-4EA4-BFC9-25157CBDC281}.Release|Any CPU.ActiveCfg = Release|Win32
- /// {A6F99D27-47B9-4EA4-BFC9-25157CBDC281}.Release|Mixed Platforms.ActiveCfg = Release|Win32
- /// {A6F99D27-47B9-4EA4-BFC9-25157CBDC281}.Release|Mixed Platforms.Build.0 = Release|Win32
- /// {A6F99D27-47B9-4EA4-BFC9-25157CBDC281}.Release|Win32.ActiveCfg = Release|Win32
- /// {A6F99D27-47B9-4EA4-BFC9-25157CBDC281}.Release|Win32.Build.0 = Release|Win32
- /// EndGlobalSection
- ///
- /// An unprocessed hashtable of entries in this section
- internal Dictionary ParseProjectConfigurations()
- {
- var rawProjectConfigurationsEntries = new Dictionary(StringComparer.OrdinalIgnoreCase);
-
- do
- {
- string str = ReadLine();
-
- if ((str == null) || (str == "EndGlobalSection"))
- {
- break;
- }
-
- // Ignore empty line or comment
- if (String.IsNullOrWhiteSpace(str) || str[0] == CommentStartChar)
- {
- continue;
- }
-
- string[] nameValue = str.Split('=');
-
- // There should be exactly one '=' character, separating the name and value.
- ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(nameValue.Length == 2, "SubCategoryForSolutionParsingErrors",
- new BuildEventFileInfo(FullPath, _currentLineNumber, 0), "SolutionParseInvalidProjectSolutionConfigurationEntry", str);
-
- rawProjectConfigurationsEntries[nameValue[0].Trim()] = nameValue[1].Trim();
- } while (true);
-
- return rawProjectConfigurationsEntries;
- }
-
- ///
- /// Read the project configuration information for every project in the solution, using pre-cached
- /// solution section data.
- ///
- /// Cached data from the project configuration section
- internal void ProcessProjectConfigurationSection(Dictionary rawProjectConfigurationsEntries)
- {
- // Instead of parsing the data line by line, we parse it project by project, constructing the
- // entry name (e.g. "{A6F99D27-47B9-4EA4-BFC9-25157CBDC281}.Release|Any CPU.ActiveCfg") and retrieving its
- // value from the raw data. The reason for this is that the IDE does it this way, and as the result
- // the '.' character is allowed in configuration names although it technically separates different
- // parts of the entry name string. This could lead to ambiguous results if we tried to parse
- // the entry name instead of constructing it and looking it up. Although it's pretty unlikely that
- // this would ever be a problem, it's safer to do it the same way VS IDE does it.
- foreach (ProjectInSolution project in _projectsInOrder)
- {
- // Solution folders don't have configurations
- if (project.ProjectType != SolutionProjectType.SolutionFolder)
- {
- foreach (SolutionConfigurationInSolution solutionConfiguration in _solutionConfigurations)
- {
- // The "ActiveCfg" entry defines the active project configuration in the given solution configuration
- // This entry must be present for every possible solution configuration/project combination.
- string entryNameActiveConfig = string.Format(CultureInfo.InvariantCulture, "{0}.{1}.ActiveCfg",
- project.ProjectGuid, solutionConfiguration.FullName);
-
- // The "Build.0" entry tells us whether to build the project configuration in the given solution configuration.
- // Technically, it specifies a configuration name of its own which seems to be a remnant of an initial,
- // more flexible design of solution configurations (as well as the '.0' suffix - no higher values are ever used).
- // The configuration name is not used, and the whole entry means "build the project configuration"
- // if it's present in the solution file, and "don't build" if it's not.
- string entryNameBuild = string.Format(CultureInfo.InvariantCulture, "{0}.{1}.Build.0",
- project.ProjectGuid, solutionConfiguration.FullName);
-
- if (rawProjectConfigurationsEntries.TryGetValue(entryNameActiveConfig, out string configurationPlatform))
- {
- string[] configurationPlatformParts = configurationPlatform.Split(SolutionConfigurationInSolution.ConfigurationPlatformSeparatorArray);
-
- // Project configuration may not necessarily contain the platform part. Some project support only the configuration part.
- ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(configurationPlatformParts.Length <= 2, "SubCategoryForSolutionParsingErrors",
- new BuildEventFileInfo(FullPath), "SolutionParseInvalidProjectSolutionConfigurationEntry",
- $"{entryNameActiveConfig} = {configurationPlatform}");
-
- var projectConfiguration = new ProjectConfigurationInSolution(
- configurationPlatformParts[0],
- (configurationPlatformParts.Length > 1) ? configurationPlatformParts[1] : string.Empty,
- rawProjectConfigurationsEntries.ContainsKey(entryNameBuild)
- );
-
- project.SetProjectConfiguration(solutionConfiguration.FullName, projectConfiguration);
- }
- }
- }
- }
- }
-
- ///
- /// Gets the default configuration name for this solution. Usually it's Debug, unless it's not present
- /// in which case it's the first configuration name we find.
- ///
- public string GetDefaultConfigurationName()
- {
- // Have we done this already? Return the cached name
- if (_defaultConfigurationName != null)
- {
- return _defaultConfigurationName;
- }
-
- _defaultConfigurationName = string.Empty;
-
- // Pick the Debug configuration as default if present
- foreach (SolutionConfigurationInSolution solutionConfiguration in SolutionConfigurations)
- {
- if (string.Equals(solutionConfiguration.ConfigurationName, "Debug", StringComparison.OrdinalIgnoreCase))
- {
- _defaultConfigurationName = solutionConfiguration.ConfigurationName;
- break;
- }
- }
-
- // Failing that, just pick the first configuration name as default
- if ((_defaultConfigurationName.Length == 0) && (SolutionConfigurations.Count > 0))
- {
- _defaultConfigurationName = SolutionConfigurations[0].ConfigurationName;
- }
-
- return _defaultConfigurationName;
- }
-
- ///
- /// Gets the default platform name for this solution. Usually it's Mixed Platforms, unless it's not present
- /// in which case it's the first platform name we find.
- ///
- public string GetDefaultPlatformName()
- {
- // Have we done this already? Return the cached name
- if (_defaultPlatformName != null)
- {
- return _defaultPlatformName;
- }
-
- _defaultPlatformName = string.Empty;
-
- // Pick the Mixed Platforms platform as default if present
- foreach (SolutionConfigurationInSolution solutionConfiguration in SolutionConfigurations)
- {
- if (string.Equals(solutionConfiguration.PlatformName, "Mixed Platforms", StringComparison.OrdinalIgnoreCase))
- {
- _defaultPlatformName = solutionConfiguration.PlatformName;
- break;
- }
- // We would like this to be chosen if Mixed platforms does not exist.
- else if (string.Equals(solutionConfiguration.PlatformName, "Any CPU", StringComparison.OrdinalIgnoreCase))
- {
- _defaultPlatformName = solutionConfiguration.PlatformName;
- }
- }
-
- // Failing that, just pick the first platform name as default
- if ((_defaultPlatformName.Length == 0) && (SolutionConfigurations.Count > 0))
- {
- _defaultPlatformName = SolutionConfigurations[0].PlatformName;
- }
-
- return _defaultPlatformName;
- }
-
- ///
- /// This method takes a string representing one of the project's unique names (guid), and
- /// returns the corresponding "friendly" name for this project.
- ///
- ///
- ///
- internal string GetProjectUniqueNameByGuid(string projectGuid)
- {
- if (_projects.TryGetValue(projectGuid, out ProjectInSolution proj))
- {
- return proj.GetUniqueProjectName();
- }
-
- return null;
- }
-
- ///
- /// This method takes a string representing one of the project's unique names (guid), and
- /// returns the corresponding relative path to this project.
- ///
- ///
- ///
- internal string GetProjectRelativePathByGuid(string projectGuid)
- {
- if (_projects.TryGetValue(projectGuid, out ProjectInSolution proj))
- {
- return proj.RelativePath;
- }
-
- return null;
- }
-
- #endregion
- } // class SolutionParser
-} // namespace Microsoft.Build.Construction
diff --git a/src/Ionide.ProjInfo.Sln/vendor/StringBuilderCache.cs b/src/Ionide.ProjInfo.Sln/vendor/StringBuilderCache.cs
deleted file mode 100644
index 3d74afb2..00000000
--- a/src/Ionide.ProjInfo.Sln/vendor/StringBuilderCache.cs
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-/*============================================================
-**
-**
-** Purpose: provide a cached reusable instance of StringBuilder
-** per thread it's an optimization that reduces the
-** number of instances constructed and collected.
-**
-** Acquire - is used to get a string builder to use of a
-** particular size. It can be called any number of
-** times, if a StringBuilder is in the cache then
-** it will be returned and the cache emptied.
-** subsequent calls will return a new StringBuilder.
-**
-** A StringBuilder instance is cached in
-** Thread Local Storage and so there is one per thread
-**
-** Release - Place the specified builder in the cache if it is
-** not too big.
-** The StringBuilder should not be used after it has
-** been released.
-** Unbalanced Releases are perfectly acceptable. It
-** will merely cause the runtime to create a new
-** StringBuilder next time Acquire is called.
-**
-** GetStringAndRelease
-** - ToString() the StringBuilder, Release it to the
-** cache and return the resulting string
-**
-===========================================================*/
-
-using System;
-using System.Text;
-
-namespace Ionide.ProjInfo.Sln.Shared
-{
- internal static class StringBuilderCache
- {
- // The value 360 was chosen in discussion with performance experts as a compromise between using
- // as little memory (per thread) as possible and still covering a large part of short-lived
- // StringBuilder creations on the startup path of VS designers.
- private const int MAX_BUILDER_SIZE = 360;
-
- [ThreadStatic]
- private static StringBuilder t_cachedInstance;
-
- public static StringBuilder Acquire(int capacity = 16 /*StringBuilder.DefaultCapacity*/)
- {
- if (capacity <= MAX_BUILDER_SIZE)
- {
- StringBuilder sb = StringBuilderCache.t_cachedInstance;
- if (sb != null)
- {
- // Avoid StringBuilder block fragmentation by getting a new StringBuilder
- // when the requested size is larger than the current capacity
- if (capacity <= sb.Capacity)
- {
- StringBuilderCache.t_cachedInstance = null;
- sb.Length = 0; // Equivalent of sb.Clear() that works on .Net 3.5
- return sb;
- }
- }
- }
- return new StringBuilder(capacity);
- }
-
- public static void Release(StringBuilder sb)
- {
- if (sb.Capacity <= MAX_BUILDER_SIZE)
- {
- StringBuilderCache.t_cachedInstance = sb;
- }
- }
-
- public static string GetStringAndRelease(StringBuilder sb)
- {
- string result = sb.ToString();
- Release(sb);
- return result;
- }
- }
-}
diff --git a/src/Ionide.ProjInfo.Sln/vendor/Traits.cs b/src/Ionide.ProjInfo.Sln/vendor/Traits.cs
deleted file mode 100644
index 06cbb41f..00000000
--- a/src/Ionide.ProjInfo.Sln/vendor/Traits.cs
+++ /dev/null
@@ -1,122 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-//-----------------------------------------------------------------------
-//
-
-using System;
-using Ionide.ProjInfo.Sln.Shared;
-
-namespace Ionide.ProjInfo.Sln.Utilities
-{
- ///
- /// Represents toggleable features of the MSBuild engine
- ///
- internal class Traits
- {
- public static Traits Instance = new Traits();
-
- public EscapeHatches EscapeHatches { get; }
-
- ///
- /// Do not expand wildcards that match a certain pattern
- ///
- public readonly bool UseLazyWildCardEvaluation = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MsBuildSkipEagerWildCardEvaluationRegexes"));
- public readonly bool LogExpandedWildcards = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDLOGEXPANDEDWILDCARDS"));
- public readonly bool CacheFileExistence = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MsBuildCacheFileExistence"));
-
- ///
- /// Eliminate locking in OpportunisticIntern at the expense of memory
- ///
- public readonly bool UseSimpleInternConcurrency = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MsBuildUseSimpleInternConcurrency"));
-
- ///
- /// Cache wildcard expansions
- ///
- public readonly bool MSBuildCacheFileEnumerations = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MsBuildCacheFileEnumerations"));
-
- public Traits()
- {
- EscapeHatches = new EscapeHatches();
- }
-
- public readonly bool EnableAllPropertyFunctions = Environment.GetEnvironmentVariable("MSBUILDENABLEALLPROPERTYFUNCTIONS") == "1";
- }
-
- internal class EscapeHatches
- {
- ///
- /// Always use the accurate-but-slow CreateFile approach to timestamp extraction.
- ///
- public readonly bool AlwaysUseContentTimestamp = Environment.GetEnvironmentVariable("MSBUILDALWAYSCHECKCONTENTTIMESTAMP") == "1";
-
- public readonly bool LogProjectImports = Environment.GetEnvironmentVariable("MSBUILDLOGIMPORTS") == "1";
-
- ///
- /// Read information only once per file per ResolveAssemblyReference invocation.
- ///
- public readonly bool CacheAssemblyInformation = Environment.GetEnvironmentVariable("MSBUILDDONOTCACHERARASSEMBLYINFORMATION") != "1";
-
- public readonly ProjectInstanceTranslationMode? ProjectInstanceTranslation = ComputeProjectInstanceTranslation();
-
- ///
- /// Never use the slow (but more accurate) CreateFile approach to timestamp extraction.
- ///
- public readonly bool UseSymlinkTimeInsteadOfTargetTime = Environment.GetEnvironmentVariable("MSBUILDUSESYMLINKTIMESTAMP") == "1";
-
- ///
- /// Whether or not to ignore imports that are considered empty. See ProjectRootElement.IsEmptyXmlFile() for more info.
- ///
- public readonly bool IgnoreEmptyImports = Environment.GetEnvironmentVariable("MSBUILDIGNOREEMPTYIMPORTS") == "1";
-
- ///
- /// Whether to to respect the TreatAsLocalProperty parameter on the Project tag.
- ///
- public readonly bool IgnoreTreatAsLocalProperty = Environment.GetEnvironmentVariable("MSBUILDIGNORETREATASLOCALPROPERTY") != null;
-
- ///
- /// Whether to write information about why we evaluate to debug output.
- ///
- public readonly bool DebugEvaluation = Environment.GetEnvironmentVariable("MSBUILDDEBUGEVALUATION") != null;
-
- ///
- /// Whether to warn when we set a property for the first time, after it was previously used.
- ///
- public readonly bool WarnOnUninitializedProperty = !String.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDWARNONUNINITIALIZEDPROPERTY"));
-
- // MSBUILDUSECASESENSITIVEITEMNAMES is an escape hatch for the fix
- // for https://github.com/Microsoft/msbuild/issues/1751. It should
- // be removed (permanently set to false) after establishing that
- // it's unneeded (at least by the 16.0 timeframe).
- public readonly bool UseCaseSensitiveItemNames = Environment.GetEnvironmentVariable("MSBUILDUSECASESENSITIVEITEMNAMES") == "1";
-
- private static ProjectInstanceTranslationMode? ComputeProjectInstanceTranslation()
- {
- var mode = Environment.GetEnvironmentVariable("MSBUILD_PROJECTINSTANCE_TRANSLATION_MODE");
-
- if (mode == null)
- {
- return null;
- }
-
- if (mode.Equals("full", StringComparison.OrdinalIgnoreCase))
- {
- return ProjectInstanceTranslationMode.Full;
- }
-
- if (mode.Equals("partial", StringComparison.OrdinalIgnoreCase))
- {
- return ProjectInstanceTranslationMode.Partial;
- }
-
- ErrorUtilities.ThrowInternalError($"Invalid escape hatch for project instance translation: {mode}");
-
- return null;
- }
-
- public enum ProjectInstanceTranslationMode
- {
- Full,
- Partial
- }
- }
-}
diff --git a/src/Ionide.ProjInfo.Sln/vendor/VisualStudioConstants.cs b/src/Ionide.ProjInfo.Sln/vendor/VisualStudioConstants.cs
deleted file mode 100644
index 235f2b1a..00000000
--- a/src/Ionide.ProjInfo.Sln/vendor/VisualStudioConstants.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-//-----------------------------------------------------------------------
-//
-// Shared Visual Studio related constants.
-//-----------------------------------------------------------------------
-
-using System;
-
-namespace Ionide.ProjInfo.Sln.Shared
-{
- ///
- /// Shared Visual Studio related constants
- ///
- internal static class VisualStudioConstants
- {
- ///
- /// This is the version number of the most recent solution file format
- /// we will read. It will be the version number used in solution files
- /// by the latest version of Visual Studio.
- ///
- internal const int CurrentVisualStudioSolutionFileVersion = 12; // VS11
-
- ///
- /// This is the version number of the latest version of Visual Studio.
- ///
- ///
- /// We use it for the version of the VC PIA we try to load and to find
- /// Visual Studio registry hive that we use to find where vcbuild.exe might be.
- ///
- internal const string CurrentVisualStudioVersion = "10.0";
- }
-}
diff --git a/src/Ionide.ProjInfo/InspectSln.fs b/src/Ionide.ProjInfo/InspectSln.fs
index 87face95..eadac9ec 100644
--- a/src/Ionide.ProjInfo/InspectSln.fs
+++ b/src/Ionide.ProjInfo/InspectSln.fs
@@ -4,6 +4,10 @@ module InspectSln =
open System
open System.IO
+ open System.Threading
+ open Microsoft.VisualStudio.SolutionPersistence
+ open Microsoft.VisualStudio.SolutionPersistence.Serializer
+ open System.Text.Json
let private normalizeDirSeparators (path: string) =
match Path.DirectorySeparatorChar with
@@ -30,19 +34,33 @@ module InspectSln =
}
and SolutionItemKind =
- | MsbuildFormat of SolutionItemMsbuildConfiguration list
+ | MSBuildFormat of SolutionItemMSBuildConfiguration list
| Folder of (SolutionItem list) * (string list)
| Unsupported
| Unknown
- and SolutionItemMsbuildConfiguration = {
+ and SolutionItemMSBuildConfiguration = {
Id: string
ConfigurationName: string
PlatformName: string
}
+ let private tryLoadSolutionModel (slnFilePath: string) =
+ // use the VS library to parse the solution
+ match SolutionSerializers.GetSerializerByMoniker(slnFilePath) with
+ | null -> Error (exn $"Unsupported solution file format %s{Path.GetExtension(slnFilePath)}")
+ | serializer ->
+ try
+ let model = serializer.OpenAsync(slnFilePath, CancellationToken.None).GetAwaiter().GetResult()
+ Ok(model)
+ with
+ | ex -> Error ex
+
+ /// Parses a file on disk and returns data about its contents. Supports sln, slnf, and slnx files.
let tryParseSln (slnFilePath: string) =
- let parseSln (sln: Sln.Construction.SolutionFile) =
+ let parseSln (sln: Model.SolutionModel) (projectsToRead: string Set option) =
+ sln.DistillProjectConfigurations()
+
let slnDir = Path.GetDirectoryName slnFilePath
let makeAbsoluteFromSlnDir =
@@ -56,77 +74,79 @@ module InspectSln =
normalizeDirSeparators
>> makeAbs
- let rec parseItem (item: Sln.Construction.ProjectInSolution) =
- let parseKind (item: Sln.Construction.ProjectInSolution) =
- match item.ProjectType with
- | Sln.Construction.SolutionProjectType.KnownToBeMSBuildFormat ->
- (item.RelativePath
- |> makeAbsoluteFromSlnDir),
- SolutionItemKind.MsbuildFormat []
- | Sln.Construction.SolutionProjectType.SolutionFolder ->
- let children =
- sln.ProjectsInOrder
- |> Seq.filter (fun x -> x.ParentProjectGuid = item.ProjectGuid)
- |> Seq.map parseItem
- |> List.ofSeq
-
- let files =
- item.FolderFiles
- |> Seq.map makeAbsoluteFromSlnDir
- |> List.ofSeq
-
- item.ProjectName, SolutionItemKind.Folder(children, files)
- | Sln.Construction.SolutionProjectType.EtpSubProject
- | Sln.Construction.SolutionProjectType.WebDeploymentProject
- | Sln.Construction.SolutionProjectType.WebProject ->
- (item.ProjectName
- |> makeAbsoluteFromSlnDir),
- SolutionItemKind.Unsupported
- | Sln.Construction.SolutionProjectType.Unknown
- | _ ->
- (item.ProjectName
- |> makeAbsoluteFromSlnDir),
- SolutionItemKind.Unknown
-
- let name, itemKind = parseKind item
+ let parseItem (item: Model.SolutionItemModel): SolutionItem =
+ {
+ Guid = item.Id
+ Name = ""
+ Kind = SolutionItemKind.Unknown
+ }
+
+ let parseProject (project: Model.SolutionProjectModel): SolutionItem =
+ { Guid = project.Id
+ Name= makeAbsoluteFromSlnDir project.FilePath
+ Kind = SolutionItemKind.MSBuildFormat [] // TODO: could theoretically parse configurations here
+ }
+ let parseFolder (folder: Model.SolutionFolderModel): SolutionItem =
{
- Guid =
- item.ProjectGuid
- |> Guid.Parse
- Name = name
- Kind = itemKind
+ Guid = folder.Id
+ Name = makeAbsoluteFromSlnDir folder.Path
+ Kind =
+ SolutionItemKind.Folder (
+ sln.SolutionItems |> Seq.filter (fun item -> not (isNull item.Parent) && item.Parent.Id = folder.Id) |> Seq.map (fun p -> parseItem p, string p.Id) |> List.ofSeq |> List.unzip)
}
- let items =
- sln.ProjectsInOrder
- |> Seq.filter (fun x -> isNull x.ParentProjectGuid)
- |> Seq.map parseItem
+ // three kinds of items - projects, folders, items
+ // yield them all here
+ let projectsWeCareAbout =
+ match projectsToRead with
+ | None -> sln.SolutionProjects :> seq<_>
+ | Some filteredProjects -> sln.SolutionProjects |> Seq.filter (fun slnProject -> filteredProjects.Contains(makeAbsoluteFromSlnDir slnProject.FilePath))
+
+ let allItems =
+ [
+ yield! projectsWeCareAbout |> Seq.map parseProject
+ yield! sln.SolutionFolders |> Seq.map parseFolder
+ yield! sln.SolutionItems |> Seq.filter (fun item -> isNull item.Parent) |> Seq.map parseItem
+ ]
let data = {
- Items =
- items
- |> List.ofSeq
+ Items = allItems
Configurations = []
}
data
- try
- Ok(
- slnFilePath,
- slnFilePath
- |> Sln.Construction.SolutionFile.Parse
- |> parseSln
- )
- with ex ->
- Error ex
+ let parseSlnf (slnfPath: string) =
+ let (slnFilePath: string, projectsToRead: string Set) =
+ let options = new JsonDocumentOptions(AllowTrailingCommas = true, CommentHandling = JsonCommentHandling.Skip)
+ let text = JsonDocument.Parse(File.ReadAllText(slnfPath), options)
+ let solutionElement = text.RootElement.GetProperty("solution")
+ let slnPath = solutionElement.GetProperty("path").GetString();
+ let projects =
+ solutionElement.GetProperty("projects").EnumerateArray()
+ |> Seq.map (fun p -> p.GetString())
+ |> Set.ofSeq
+ slnPath, projects
+
+ match tryLoadSolutionModel slnFilePath with
+ | Ok sln ->
+ Ok (parseSln sln (Some projectsToRead))
+ | Error ex ->
+ Error ex
+
+ if slnFilePath.EndsWith(".slnf") then
+ parseSlnf slnFilePath
+ else
+ match tryLoadSolutionModel slnFilePath with
+ | Ok sln -> Ok(parseSln sln None)
+ | Error ex -> Error ex
let loadingBuildOrder (data: SolutionData) =
let rec projs (item: SolutionItem) =
match item.Kind with
- | MsbuildFormat items -> [ item.Name ]
+ | MSBuildFormat items -> [ item.Name ]
| Folder(items, _) ->
items
|> List.collect projs
diff --git a/src/Ionide.ProjInfo/Ionide.ProjInfo.fsproj b/src/Ionide.ProjInfo/Ionide.ProjInfo.fsproj
index ab313443..2f545630 100644
--- a/src/Ionide.ProjInfo/Ionide.ProjInfo.fsproj
+++ b/src/Ionide.ProjInfo/Ionide.ProjInfo.fsproj
@@ -12,13 +12,11 @@
-
-
-
+
diff --git a/src/Ionide.ProjInfo/Library.fs b/src/Ionide.ProjInfo/Library.fs
index 92a1a75c..c04c078d 100644
--- a/src/Ionide.ProjInfo/Library.fs
+++ b/src/Ionide.ProjInfo/Library.fs
@@ -1509,7 +1509,7 @@ type WorkspaceLoader private (toolsPath: ToolsPath, ?globalProperties: (string *
member this.LoadSln(sln, customProperties: string list, binaryLogs) =
match InspectSln.tryParseSln sln with
- | Ok(_, slnData) ->
+ | Ok(slnData) ->
let solutionProjects = InspectSln.loadingBuildOrder slnData
this.LoadProjects(solutionProjects, customProperties, binaryLogs)
| Error d -> failwithf "Cannot load the sln: %A" d
diff --git a/test/Ionide.ProjInfo.Tests/Tests.fs b/test/Ionide.ProjInfo.Tests/Tests.fs
index 1458f514..ad24c6b1 100644
--- a/test/Ionide.ProjInfo.Tests/Tests.fs
+++ b/test/Ionide.ProjInfo.Tests/Tests.fs
@@ -816,7 +816,7 @@ let testParseSln toolsPath =
let actualProjects =
InspectSln.loadingBuildOrder (
match p with
- | Ok(_, data) -> data
+ | Ok(data) -> data
| _ -> failwith "unreachable"
)
|> List.map Path.GetFullPath
@@ -1999,12 +1999,6 @@ let debugTests toolsPath workspaceLoader (workspaceFactory: ToolsPath -> IWorksp
(fun logger fs ->
let loader = workspaceFactory toolsPath
-
- // let projPath = @"D:\Programowanie\Projekty\Ionide\dotnet-proj-info\src\Ionide.ProjInfo.Sln\Ionide.ProjInfo.Sln.csproj"
- // let parsedProjs = loader.LoadProjects [ projPath ] |> Seq.toList
-
- // printfn "%A" parsedProjs
-
let slnPath =
@"C:\Users\JimmyByrd\Documents\Repositories\public\TheAngryByrd\FsToolkit.ErrorHandling\FsToolkit.ErrorHandling.sln"