From a663ecc232f9e2dc6375dca17c2cab8d2ac8b550 Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Thu, 2 Jan 2025 20:07:00 -0600 Subject: [PATCH] replace homegrown sln parser with library --- CHANGELOG.md | 6 + Directory.Packages.props | 16 +- README.md | 1 - ionide-proj-info.sln | 15 - .../WorkspacePeek.fs | 42 +- src/Ionide.ProjInfo.Sln/ExceptionHandling.cs | 35 - src/Ionide.ProjInfo.Sln/FileUtilities.cs | 1424 -------------- .../Ionide.ProjInfo.Sln.csproj | 10 - src/Ionide.ProjInfo.Sln/NativeMethods.cs | 1625 ---------------- .../ProbablyUnusedNamespaces.cs | 6 - src/Ionide.ProjInfo.Sln/README.md | 27 - src/Ionide.ProjInfo.Sln/Reimplemented.cs | 22 - src/Ionide.ProjInfo.Sln/ResourcesRelated.cs | 39 - .../vendor/BuildEventFileInfo.cs | 190 -- src/Ionide.ProjInfo.Sln/vendor/Constants.cs | 147 -- .../vendor/ErrorUtilities.cs | 801 -------- .../vendor/EscapingUtilities.cs | 304 --- .../vendor/InvalidProjectFileException.cs | 382 ---- .../vendor/OpportunisticIntern.cs | 1219 ------------ .../vendor/ProjectConfigurationInSolution.cs | 62 - .../vendor/ProjectFileErrorUtilities.cs | 154 -- .../vendor/ProjectInSolution.cs | 557 ------ .../vendor/SolutionConfigurationInSolution.cs | 58 - .../vendor/SolutionFile.cs | 1683 ----------------- .../vendor/StringBuilderCache.cs | 84 - src/Ionide.ProjInfo.Sln/vendor/Traits.cs | 122 -- .../vendor/VisualStudioConstants.cs | 33 - src/Ionide.ProjInfo/InspectSln.fs | 136 +- src/Ionide.ProjInfo/Ionide.ProjInfo.fsproj | 4 +- src/Ionide.ProjInfo/Library.fs | 2 +- test/Ionide.ProjInfo.Tests/Tests.fs | 8 +- 31 files changed, 107 insertions(+), 9107 deletions(-) delete mode 100644 src/Ionide.ProjInfo.Sln/ExceptionHandling.cs delete mode 100644 src/Ionide.ProjInfo.Sln/FileUtilities.cs delete mode 100644 src/Ionide.ProjInfo.Sln/Ionide.ProjInfo.Sln.csproj delete mode 100644 src/Ionide.ProjInfo.Sln/NativeMethods.cs delete mode 100644 src/Ionide.ProjInfo.Sln/ProbablyUnusedNamespaces.cs delete mode 100644 src/Ionide.ProjInfo.Sln/README.md delete mode 100644 src/Ionide.ProjInfo.Sln/Reimplemented.cs delete mode 100644 src/Ionide.ProjInfo.Sln/ResourcesRelated.cs delete mode 100644 src/Ionide.ProjInfo.Sln/vendor/BuildEventFileInfo.cs delete mode 100644 src/Ionide.ProjInfo.Sln/vendor/Constants.cs delete mode 100644 src/Ionide.ProjInfo.Sln/vendor/ErrorUtilities.cs delete mode 100644 src/Ionide.ProjInfo.Sln/vendor/EscapingUtilities.cs delete mode 100644 src/Ionide.ProjInfo.Sln/vendor/InvalidProjectFileException.cs delete mode 100644 src/Ionide.ProjInfo.Sln/vendor/OpportunisticIntern.cs delete mode 100644 src/Ionide.ProjInfo.Sln/vendor/ProjectConfigurationInSolution.cs delete mode 100644 src/Ionide.ProjInfo.Sln/vendor/ProjectFileErrorUtilities.cs delete mode 100644 src/Ionide.ProjInfo.Sln/vendor/ProjectInSolution.cs delete mode 100644 src/Ionide.ProjInfo.Sln/vendor/SolutionConfigurationInSolution.cs delete mode 100644 src/Ionide.ProjInfo.Sln/vendor/SolutionFile.cs delete mode 100644 src/Ionide.ProjInfo.Sln/vendor/StringBuilderCache.cs delete mode 100644 src/Ionide.ProjInfo.Sln/vendor/Traits.cs delete mode 100644 src/Ionide.ProjInfo.Sln/vendor/VisualStudioConstants.cs 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"