From bf417423e028fee4c403ef21414080750e46d96b Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Wed, 30 Aug 2023 10:43:30 -0500 Subject: [PATCH 1/8] Enable .NET SDK package validation (#240) --- src/MSBuildLocator/Microsoft.Build.Locator.csproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/MSBuildLocator/Microsoft.Build.Locator.csproj b/src/MSBuildLocator/Microsoft.Build.Locator.csproj index 056f7ca5..668ae489 100644 --- a/src/MSBuildLocator/Microsoft.Build.Locator.csproj +++ b/src/MSBuildLocator/Microsoft.Build.Locator.csproj @@ -14,6 +14,9 @@ MSBuild Locator Package that assists in locating and using a copy of MSBuild installed as part of Visual Studio 2017 or higher or .NET Core SDK 2.1 or higher. + + true + 1.6.1 From 334346adb15b0c95541db485fb87a9fda4eda50d Mon Sep 17 00:00:00 2001 From: YuliiaKovalova <95473390+YuliiaKovalova@users.noreply.github.com> Date: Wed, 30 Aug 2023 18:32:21 +0200 Subject: [PATCH 2/8] apply code style rules (#234) --- .editorconfig | 400 ++++++++++++++++++ MSBuildLocator.sln | 1 + samples/BuilderApp/Program.cs | 45 +- .../Microsoft.Build.Locator.Tests.csproj | 2 +- src/MSBuildLocator.Tests/QueryOptionsTests.cs | 4 +- .../SemanticVersionParserTests.cs | 59 +-- src/MSBuildLocator/DiscoveryType.cs | 2 +- src/MSBuildLocator/DotNetSdkLocationHelper.cs | 147 +++---- src/MSBuildLocator/MSBuildLocator.cs | 90 ++-- src/MSBuildLocator/NativeMethods.cs | 6 +- src/MSBuildLocator/Properties/AssemblyInfo.cs | 2 +- src/MSBuildLocator/Utils/SemanticVersion.cs | 20 +- .../Utils/SemanticVersionParser.cs | 7 +- src/MSBuildLocator/Utils/VersionComparer.cs | 22 +- src/MSBuildLocator/VisualStudioInstance.cs | 2 +- .../VisualStudioLocationHelper.cs | 26 +- 16 files changed, 589 insertions(+), 246 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..1111154c --- /dev/null +++ b/.editorconfig @@ -0,0 +1,400 @@ +# editorconfig.org + +# top-most EditorConfig file +root = true + +# Default settings: +# A newline ending every file +# Use 4 spaces as indentation +[*] +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +[project.json] +indent_size = 2 + +# C# files +[*.cs] +# New line preferences +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_switch_labels = true +csharp_indent_labels = one_less_than_current + +# Modifier preferences +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion + +# avoid this. unless absolutely necessary +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_property = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_event = false:suggestion + +# Types: use keywords instead of BCL types, and permit var only when the type is clear +csharp_style_var_for_built_in_types = false:suggestion +csharp_style_var_when_type_is_apparent = false:none +csharp_style_var_elsewhere = false:suggestion +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion + +# name all constant fields using PascalCase +dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.required_modifiers = const +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +# static fields should have s_ prefix +dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion +dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields +dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style +dotnet_naming_symbols.static_fields.applicable_kinds = field +dotnet_naming_symbols.static_fields.required_modifiers = static +dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected +dotnet_naming_style.static_prefix_style.required_prefix = s_ +dotnet_naming_style.static_prefix_style.capitalization = camel_case + +# internal and private fields should be _camelCase +dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion +dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields +dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style +dotnet_naming_symbols.private_internal_fields.applicable_kinds = field +dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal +dotnet_naming_style.camel_case_underscore_style.required_prefix = _ +dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case + +# Code style defaults +csharp_using_directive_placement = outside_namespace:suggestion +dotnet_sort_system_directives_first = true +csharp_prefer_braces = true:silent +csharp_preserve_single_line_blocks = true:none +csharp_preserve_single_line_statements = false:none +csharp_prefer_static_local_function = true:suggestion +csharp_prefer_simple_using_statement = false:none +csharp_style_prefer_switch_expression = true:suggestion + +# Code quality +dotnet_style_readonly_field = true:suggestion +dotnet_code_quality_unused_parameters = non_public:suggestion + +# Expression-level preferences +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_auto_properties = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +csharp_prefer_simple_default_expression = true:suggestion + +# Expression-bodied members +csharp_style_expression_bodied_methods = true:silent +csharp_style_expression_bodied_constructors = true:silent +csharp_style_expression_bodied_operators = true:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = true:silent + +# Pattern matching +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion + +# Null checking preferences +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion + +# Other features +csharp_style_prefer_index_operator = false:none +csharp_style_prefer_range_operator = false:none +csharp_style_pattern_local_over_anonymous_function = false:none + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = do_not_ignore +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Analyzers +dotnet_code_quality.ca1802.api_surface = private, internal +dotnet_code_quality.ca2208.api_surface = public + +# CA1852: Seal internal types +dotnet_diagnostic.ca1852.severity = warning + +# RS0037: Enable tracking of nullability of reference types in the declared API +# Our API is not annotated but new classes get nullable enabled so disable this. +# We'd be happy if everything was annotated and this could be removed. +dotnet_diagnostic.RS0037.severity = none + +# License header +file_header_template = Copyright (c) Microsoft. All rights reserved.\nLicensed under the MIT license. See LICENSE file in the project root for full license information. + +# C++ Files +[*.{cpp,h,in}] +curly_bracket_next_line = true +indent_brace_style = Allman + +# Xml project files +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}] +indent_size = 2 + +[*.{csproj,vbproj,proj,nativeproj,locproj}] +charset = utf-8 + +# Xml build files +[*.builds] +indent_size = 2 + +# Xml files +[*.{xml,stylecop,resx,ruleset}] +indent_size = 2 + +# Xml config files +[*.{props,targets,config,nuspec}] +indent_size = 2 + +# YAML config files +[*.{yml,yaml}] +indent_size = 2 + +# Shell scripts +[*.sh] +end_of_line = lf +[*.{cmd, bat}] +end_of_line = crlf + +[src/**/*.{cs,vb}] +# Code style checks +dotnet_analyzer_diagnostic.category-Style.severity = warning + +# Cast is redundant +dotnet_diagnostic.IDE0004.severity = suggestion + +# IDE0005: Remove unnecessary usings/imports +dotnet_diagnostic.IDE0005.severity = none + +# Use explicit type instead of 'var' +dotnet_diagnostic.IDE0008.severity = suggestion + +# Populate switch +dotnet_diagnostic.IDE0010.severity = suggestion + +# Null check can be simplified +dotnet_diagnostic.IDE0016.severity = suggestion + +# Object initialization can be simplified +dotnet_diagnostic.IDE0017.severity = suggestion + +# Variable declaration can be inlined +dotnet_diagnostic.IDE0018.severity = suggestion + +# Use pattern matching +dotnet_diagnostic.IDE0019.severity = suggestion +dotnet_diagnostic.IDE0020.severity = suggestion + +# Use expression body for constructor +dotnet_diagnostic.IDE0021.severity = suggestion + +# Use expression body for method +dotnet_diagnostic.IDE0022.severity = suggestion + +# Use expression body for conversion operator +dotnet_diagnostic.IDE0023.severity = suggestion + +# Use block body for operator +dotnet_diagnostic.IDE0024.severity = suggestion + +# Use expression body for property +dotnet_diagnostic.IDE0025.severity = suggestion + +# Use expression body for indexer +dotnet_diagnostic.IDE0026.severity = suggestion + +# Use expression body for accessor +dotnet_diagnostic.IDE0027.severity = suggestion + +# Collection initialization can be simplified +dotnet_diagnostic.IDE0028.severity = suggestion + +# Null check can be simplified +dotnet_diagnostic.IDE0031.severity = suggestion + +# Use auto property +dotnet_diagnostic.IDE0032.severity = suggestion + +# 'default' expression can be simplified +dotnet_diagnostic.IDE0034.severity = suggestion + +# Member name can be simplified +dotnet_diagnostic.IDE0037.severity = suggestion + +# Use local function +dotnet_diagnostic.IDE0039.severity = suggestion + +# Null check can be simplified +dotnet_diagnostic.IDE0041.severity = suggestion + +# Variable declaration can be deconstructed +dotnet_diagnostic.IDE0042.severity = suggestion + +# Made field readonly +dotnet_diagnostic.IDE0044.severity = suggestion + +# 'if' statement can be simplified +dotnet_diagnostic.IDE0045.severity = suggestion +dotnet_diagnostic.IDE0046.severity = suggestion + +# Parentheses can be removed +dotnet_diagnostic.IDE0047.severity = suggestion + +# Parentheses should be added for clarity +dotnet_diagnostic.IDE0048.severity = suggestion + +# Member name can be simplified +dotnet_diagnostic.IDE0049.severity = suggestion + +# Use compound assignment +dotnet_diagnostic.IDE0054.severity = suggestion + +# Fix formatting +dotnet_diagnostic.IDE0055.severity = suggestion + +# Indexing can be simplified +dotnet_diagnostic.IDE0056.severity = suggestion + +# Slice can be simplified +dotnet_diagnostic.IDE0057.severity = suggestion + +# Expression value is never used +dotnet_diagnostic.IDE0058.severity = suggestion + +# Unnecessary assignment of a value +dotnet_diagnostic.IDE0059.severity = suggestion + +# Remove unused parameter +dotnet_diagnostic.IDE0060.severity = suggestion + +# Use expression body for a local function +dotnet_diagnostic.IDE0061.severity = suggestion + +# Local function can be made static +dotnet_diagnostic.IDE0062.severity = suggestion + +# Using directives must be placed outside of a namespace declaration +dotnet_diagnostic.IDE0065.severity = suggestion + +# Use 'switch' expression +dotnet_diagnostic.IDE0066.severity = suggestion + +# 'GetHashCode' implementation can be simplified +dotnet_diagnostic.IDE0070.severity = suggestion + +# Interpolation can be simplified +dotnet_diagnostic.IDE0071.severity = suggestion + +# Populate switch +dotnet_diagnostic.IDE0072.severity = suggestion + +# Use compound assignment +dotnet_diagnostic.IDE0074.severity = suggestion + +# Conditional expression can be simplified +dotnet_diagnostic.IDE0075.severity = suggestion + +# Use pattern matching +dotnet_diagnostic.IDE0078.severity = suggestion +dotnet_diagnostic.IDE0083.severity = suggestion + +# 'typeof' can be converted to 'nameof' +dotnet_diagnostic.IDE0082.severity = suggestion + +# 'new' expression can be simplified +dotnet_diagnostic.IDE0090.severity = suggestion + +# Simplify LINQ expression +dotnet_diagnostic.IDE0120.severity = suggestion + +# namespace does not match folder structure +dotnet_diagnostic.IDE0130.severity = suggestion + +# Null check can be clarified +dotnet_diagnostic.IDE0150.severity = suggestion + +# Convert to block scoped namespaces +dotnet_diagnostic.IDE0160.severity = suggestion + +# Simplify property pattern +dotnet_diagnostic.IDE0170.severity = suggestion + +# Use tuple to swap values +dotnet_diagnostic.IDE0180.severity = suggestion + +# Use tuple to swap values +dotnet_diagnostic.IDE0180.severity = suggestion + +# Lambda expression can be removed +dotnet_diagnostic.IDE0200.severity = suggestion + +# Convert to top-level statements +dotnet_diagnostic.IDE0210.severity = suggestion + +# 'foreach' statement implicitly converts +dotnet_diagnostic.IDE0220.severity = suggestion + +# Use UTF-8 string literal +dotnet_diagnostic.IDE0230.severity = suggestion + +# Nullable directives +dotnet_diagnostic.IDE0240.severity = suggestion +dotnet_diagnostic.IDE0241.severity = suggestion + +# Struct can be made 'readonly' +dotnet_diagnostic.IDE0250.severity = suggestion + +# Struct methods can be made 'readonly' +dotnet_diagnostic.IDE0251.severity = suggestion + +# Null check can be simplified +dotnet_diagnostic.IDE0270.severity = suggestion + +# naming rule violation +dotnet_diagnostic.IDE1006.severity = suggestion diff --git a/MSBuildLocator.sln b/MSBuildLocator.sln index 4540e62d..68f161de 100644 --- a/MSBuildLocator.sln +++ b/MSBuildLocator.sln @@ -13,6 +13,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject Directory.Build.props = Directory.Build.props version.json = version.json + .editorconfig = .editorconfig EndProjectSection EndProject Global diff --git a/samples/BuilderApp/Program.cs b/samples/BuilderApp/Program.cs index 0ec2f0bc..56d8ccc4 100644 --- a/samples/BuilderApp/Program.cs +++ b/samples/BuilderApp/Program.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.IO; using System.Linq; +using System.Reflection; using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; using Microsoft.Build.Framework; @@ -13,37 +14,37 @@ namespace BuilderApp { - internal class Program + internal sealed class Program { private static void Main(string[] args) { Header(); - var projectFilePath = Args(args); + string projectFilePath = Args(args); // Before we can build we need to resolve MSBuild assemblies. We could: // 1) Use defaults and call: MSBuildLocator.RegisterDefaults(); // 2) Do something fancier and ask the user. As an example we'll do that. var instances = MSBuildLocator.QueryVisualStudioInstances().ToList(); - var msbuildDeploymentToUse = AskWhichMSBuildToUse(instances); + (VisualStudioInstance VSInstance, string MSBuildPath) = AskWhichMSBuildToUse(instances); // Calling Register methods will subscribe to AssemblyResolve event. After this we can // safely call code that use MSBuild types (in the Builder class). - if (msbuildDeploymentToUse.VSInstance != null) + if (VSInstance != null) { - Console.WriteLine($"Using MSBuild from VS Instance: {msbuildDeploymentToUse.VSInstance.Name} - {msbuildDeploymentToUse.VSInstance.Version}"); + Console.WriteLine($"Using MSBuild from VS Instance: {VSInstance.Name} - {VSInstance.Version}"); Console.WriteLine(); - MSBuildLocator.RegisterInstance(msbuildDeploymentToUse.VSInstance); + MSBuildLocator.RegisterInstance(VSInstance); } else { - Console.WriteLine($"Using MSBuild from path: {msbuildDeploymentToUse.MSBuildPath}"); + Console.WriteLine($"Using MSBuild from path: {MSBuildPath}"); Console.WriteLine(); - MSBuildLocator.RegisterMSBuildPath(msbuildDeploymentToUse.MSBuildPath); + MSBuildLocator.RegisterMSBuildPath(MSBuildPath); } - var result = new Builder().Build(projectFilePath); + bool result = Builder.Build(projectFilePath); Console.WriteLine(); Console.ForegroundColor = result ? ConsoleColor.Green : ConsoleColor.Red; @@ -59,10 +60,10 @@ private static (VisualStudioInstance VSInstance, string MSBuildPath) AskWhichMSB } Console.WriteLine($"0) Custom path"); - for (var i = 1; i <= instances.Count; i++) + for (int i = 1; i <= instances.Count; i++) { - var instance = instances[i - 1]; - var recommended = string.Empty; + VisualStudioInstance instance = instances[i - 1]; + string recommended = string.Empty; // The dev console is probably always the right choice because the user explicitly opened // one associated with a Visual Studio install. It will always be first in the list. @@ -74,27 +75,27 @@ private static (VisualStudioInstance VSInstance, string MSBuildPath) AskWhichMSB Console.WriteLine(); Console.WriteLine("Select an instance of MSBuild: "); - var answer = Console.ReadLine(); + string answer = Console.ReadLine(); if (int.TryParse(answer, out int instanceChoice) && instanceChoice >= 0 && instanceChoice <= instances.Count) { if (instanceChoice == 0) { Console.WriteLine("Input path to MSBuild deployment:"); - var msbuildPath = Console.ReadLine(); + string msBuildPath = Console.ReadLine(); - if (!Directory.Exists(msbuildPath)) + if (!Directory.Exists(msBuildPath)) { - Console.WriteLine($"Directory does not exist: {msbuildPath}"); + Console.WriteLine($"Directory does not exist: {msBuildPath}"); Environment.Exit(-1); } - return (null, msbuildPath); + return (null, msBuildPath); } else { - var instanceUsed = instances[instanceChoice - 1]; + VisualStudioInstance instanceUsed = instances[instanceChoice - 1]; return (instanceUsed, null); } } @@ -116,7 +117,7 @@ private static void Header() private static string Args(string[] args) { if (args.Length < 1 || !File.Exists(args[0])) Usage(); - var projectFilePath = args[0]; + string projectFilePath = args[0]; return projectFilePath; } @@ -139,9 +140,9 @@ private static void Usage() /// public class Builder { - public bool Build(string projectFile) + public static bool Build(string projectFile) { - var assembly = typeof(Project).Assembly; + Assembly assembly = typeof(Project).Assembly; FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(assembly.Location); Console.WriteLine(); @@ -154,7 +155,7 @@ public bool Build(string projectFile) return project.Build(new Logger()); } - private class Logger : ILogger + private sealed class Logger : ILogger { public void Initialize(IEventSource eventSource) { diff --git a/src/MSBuildLocator.Tests/Microsoft.Build.Locator.Tests.csproj b/src/MSBuildLocator.Tests/Microsoft.Build.Locator.Tests.csproj index 4c8d0abf..4114e127 100644 --- a/src/MSBuildLocator.Tests/Microsoft.Build.Locator.Tests.csproj +++ b/src/MSBuildLocator.Tests/Microsoft.Build.Locator.Tests.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/MSBuildLocator.Tests/QueryOptionsTests.cs b/src/MSBuildLocator.Tests/QueryOptionsTests.cs index c9fb62ea..842e116d 100644 --- a/src/MSBuildLocator.Tests/QueryOptionsTests.cs +++ b/src/MSBuildLocator.Tests/QueryOptionsTests.cs @@ -55,7 +55,7 @@ public void NoResultsTest() VerifyQueryResults(instances, DiscoveryType.DotNetSdk); } - private void VerifyQueryResults(IEnumerable instances, DiscoveryType discoveryTypes, params string[] expectedInstanceNames) + private static void VerifyQueryResults(IEnumerable instances, DiscoveryType discoveryTypes, params string[] expectedInstanceNames) { IEnumerable actual = MSBuildLocator.QueryVisualStudioInstances(instances, new VisualStudioInstanceQueryOptions { @@ -76,4 +76,4 @@ private void VerifyQueryResults(IEnumerable instances, Dis } } } -} \ No newline at end of file +} diff --git a/src/MSBuildLocator.Tests/SemanticVersionParserTests.cs b/src/MSBuildLocator.Tests/SemanticVersionParserTests.cs index f9e57b3f..94f73db4 100644 --- a/src/MSBuildLocator.Tests/SemanticVersionParserTests.cs +++ b/src/MSBuildLocator.Tests/SemanticVersionParserTests.cs @@ -3,6 +3,7 @@ #if NETCOREAPP +using Microsoft.Build.Locator.Utils; using Shouldly; using System.Linq; using Xunit; @@ -14,61 +15,61 @@ public class SemanticVersionParserTests [Fact] public void TryParseTest_ReleaseVersion() { - var version = "7.0.333"; + string version = "7.0.333"; - var isParsed = SemanticVersionParser.TryParse(version, out var parsedVerion); + bool isParsed = SemanticVersionParser.TryParse(version, out SemanticVersion parsedVersion); - parsedVerion.ShouldNotBeNull(); + _ = parsedVersion.ShouldNotBeNull(); isParsed.ShouldBeTrue(); - parsedVerion.Major.ShouldBe(7); - parsedVerion.Minor.ShouldBe(0); - parsedVerion.Patch.ShouldBe(333); - parsedVerion.ReleaseLabels.ShouldBeEmpty(); + parsedVersion.Major.ShouldBe(7); + parsedVersion.Minor.ShouldBe(0); + parsedVersion.Patch.ShouldBe(333); + parsedVersion.ReleaseLabels.ShouldBeEmpty(); } [Fact] public void TryParseTest_PreviewVersion() { - var version = "8.0.0-preview.6.23329.7"; + string version = "8.0.0-preview.6.23329.7"; - var isParsed = SemanticVersionParser.TryParse(version, out var parsedVerion); + bool isParsed = SemanticVersionParser.TryParse(version, out SemanticVersion parsedVersion); - parsedVerion.ShouldNotBeNull(); + _ = parsedVersion.ShouldNotBeNull(); isParsed.ShouldBeTrue(); - parsedVerion.Major.ShouldBe(8); - parsedVerion.Minor.ShouldBe(0); - parsedVerion.Patch.ShouldBe(0); - parsedVerion.ReleaseLabels.ShouldBe(new[] { "preview", "6", "23329", "7" }); + parsedVersion.Major.ShouldBe(8); + parsedVersion.Minor.ShouldBe(0); + parsedVersion.Patch.ShouldBe(0); + parsedVersion.ReleaseLabels.ShouldBe(new[] { "preview", "6", "23329", "7" }); } [Fact] public void TryParseTest_InvalidInput_LeadingZero() { - var version = "0.0-preview.6"; + string version = "0.0-preview.6"; - var isParsed = SemanticVersionParser.TryParse(version, out var parsedVerion); + bool isParsed = SemanticVersionParser.TryParse(version, out SemanticVersion parsedVersion); - Assert.Null(parsedVerion); + Assert.Null(parsedVersion); isParsed.ShouldBeFalse(); } [Fact] public void TryParseTest_InvalidInput_FourPartsVersion() { - var version = "5.0.3.4"; + string version = "5.0.3.4"; - var isParsed = SemanticVersionParser.TryParse(version, out var parsedVerion); + bool isParsed = SemanticVersionParser.TryParse(version, out SemanticVersion parsedVersion); - Assert.Null(parsedVerion); + Assert.Null(parsedVersion); isParsed.ShouldBeFalse(); } [Fact] public void VersionSortingTest_WithPreview() { - var versions = new[] { "7.0.7", "8.0.0-preview.6.23329.7", "8.0.0-preview.3.23174.8" }; + string[] versions = new[] { "7.0.7", "8.0.0-preview.6.23329.7", "8.0.0-preview.3.23174.8" }; - var maxVersion = versions.Select(v => SemanticVersionParser.TryParse(v, out var parsedVerion) ? parsedVerion : null).Max(); + SemanticVersion maxVersion = versions.Select(v => SemanticVersionParser.TryParse(v, out SemanticVersion parsedVersion) ? parsedVersion : null).Max(); maxVersion.OriginalValue.ShouldBe("8.0.0-preview.6.23329.7"); } @@ -76,9 +77,9 @@ public void VersionSortingTest_WithPreview() [Fact] public void VersionSortingTest_ReleaseOnly() { - var versions = new[] { "7.0.7", "3.7.2", "10.0.0" }; + string[] versions = new[] { "7.0.7", "3.7.2", "10.0.0" }; - var maxVersion = versions.Max(v => SemanticVersionParser.TryParse(v, out var parsedVerion) ? parsedVerion : null); + SemanticVersion maxVersion = versions.Max(v => SemanticVersionParser.TryParse(v, out SemanticVersion parsedVersion) ? parsedVersion : null); maxVersion.OriginalValue.ShouldBe("10.0.0"); } @@ -86,9 +87,9 @@ public void VersionSortingTest_ReleaseOnly() [Fact] public void VersionSortingTest_WithInvalidFolderNames() { - var versions = new[] { "7.0.7", "3.7.2", "dummy", "5.7.8.9" }; + string[] versions = new[] { "7.0.7", "3.7.2", "dummy", "5.7.8.9" }; - var maxVersion = versions.Max(v => SemanticVersionParser.TryParse(v, out var parsedVerion) ? parsedVerion : null); + SemanticVersion maxVersion = versions.Max(v => SemanticVersionParser.TryParse(v, out SemanticVersion parsedVersion) ? parsedVersion : null); maxVersion.OriginalValue.ShouldBe("7.0.7"); } @@ -96,12 +97,12 @@ public void VersionSortingTest_WithInvalidFolderNames() [Fact] public void VersionSortingTest_WithAllInvalidFolderNames() { - var versions = new[] { "dummy", "5.7.8.9" }; + string[] versions = new[] { "dummy", "5.7.8.9" }; - var maxVersion = versions.Max(v => SemanticVersionParser.TryParse(v, out var parsedVerion) ? parsedVerion : null); + SemanticVersion maxVersion = versions.Max(v => SemanticVersionParser.TryParse(v, out SemanticVersion parsedVersion) ? parsedVersion : null); maxVersion.ShouldBeNull(); } } } -#endif \ No newline at end of file +#endif diff --git a/src/MSBuildLocator/DiscoveryType.cs b/src/MSBuildLocator/DiscoveryType.cs index 7a4ad585..79bd4db9 100644 --- a/src/MSBuildLocator/DiscoveryType.cs +++ b/src/MSBuildLocator/DiscoveryType.cs @@ -27,4 +27,4 @@ public enum DiscoveryType /// DotNetSdk = 4 } -} \ No newline at end of file +} diff --git a/src/MSBuildLocator/DotNetSdkLocationHelper.cs b/src/MSBuildLocator/DotNetSdkLocationHelper.cs index 1dec2c49..b12ca840 100644 --- a/src/MSBuildLocator/DotNetSdkLocationHelper.cs +++ b/src/MSBuildLocator/DotNetSdkLocationHelper.cs @@ -6,12 +6,12 @@ using System; using System.Collections.Generic; using System.IO; -using System.IO.Enumeration; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using System.Runtime.Loader; using System.Text.RegularExpressions; +using Microsoft.Build.Locator.Utils; #nullable enable @@ -19,10 +19,10 @@ namespace Microsoft.Build.Locator { internal static class DotNetSdkLocationHelper { - private static readonly Regex VersionRegex = new Regex(@"^(\d+)\.(\d+)\.(\d+)", RegexOptions.Multiline); - private static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - private static readonly string ExeName = IsWindows ? "dotnet.exe" : "dotnet"; - private static readonly Lazy DotnetPath = new(() => ResolveDotnetPath()); + private static readonly Regex s_versionRegex = new(@"^(\d+)\.(\d+)\.(\d+)", RegexOptions.Multiline); + private static readonly bool s_isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + private static readonly string s_exeName = s_isWindows ? "dotnet.exe" : "dotnet"; + private static readonly Lazy s_dotnetPath = new(() => ResolveDotnetPath()); public static VisualStudioInstance? GetInstance(string dotNetSdkPath) { @@ -38,7 +38,7 @@ internal static class DotNetSdkLocationHelper } // Preview versions contain a hyphen after the numeric part of the version. Version.TryParse doesn't accept that. - Match versionMatch = VersionRegex.Match(File.ReadAllText(versionPath)); + Match versionMatch = s_versionRegex.Match(File.ReadAllText(versionPath)); if (!versionMatch.Success) { @@ -70,10 +70,10 @@ internal static class DotNetSdkLocationHelper } public static IEnumerable GetInstances(string workingDirectory) - { - foreach (var basePath in GetDotNetBasePaths(workingDirectory)) + { + foreach (string basePath in GetDotNetBasePaths(workingDirectory)) { - var dotnetSdk = GetInstance(basePath); + VisualStudioInstance? dotnetSdk = GetInstance(basePath); if (dotnetSdk != null) { yield return dotnetSdk; @@ -119,7 +119,7 @@ private static IEnumerable GetDotNetBasePaths(string workingDirectory) private static void ModifyUnmanagedDllResolver(Action resolverAction) { // For Windows hostfxr is loaded in the process. - if (!IsWindows) + if (!s_isWindows) { var loadContext = AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly()); if (loadContext != null) @@ -131,44 +131,37 @@ private static void ModifyUnmanagedDllResolver(Action resol private static IntPtr HostFxrResolver(Assembly assembly, string libraryName) { - // the DllImport hardcoded the name as hostfxr. - if (!libraryName.Equals(NativeMethods.HostFxrName, StringComparison.Ordinal)) + string hostFxrLibName = "libhostfxr"; + string libExtension = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "dylib" : "so"; + + if (!hostFxrLibName.Equals(libraryName)) { return IntPtr.Zero; } - string hostFxrLibName = - RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? - "hostfxr.dll" : - RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "libhostfxr.dylib" : "libhostfxr.so"; - - string hostFxrRoot = Path.Combine(DotnetPath.Value, "host", "fxr"); + string hostFxrRoot = Path.Combine(s_dotnetPath.Value, "host", "fxr"); if (Directory.Exists(hostFxrRoot)) { - var fileEnumerable = new FileSystemEnumerable( - directory: hostFxrRoot, - transform: static (ref FileSystemEntry entry) => SemanticVersionParser.TryParse(entry.FileName.ToString(), out var version) ? version : null) - { - ShouldIncludePredicate = static (ref FileSystemEntry entry) => entry.IsDirectory - }; - // Load hostfxr from the highest version, because it should be backward-compatible - if (fileEnumerable.Max() is SemanticVersion hostFxrVersion) + SemanticVersion? hostFxrAssemblyDirectory = Directory.GetDirectories(hostFxrRoot) + .Max(str => SemanticVersionParser.TryParse(str, out SemanticVersion? version) ? version : null); + + if (hostFxrAssemblyDirectory != null && !string.IsNullOrEmpty(hostFxrAssemblyDirectory.OriginalValue)) { - string hostFxrAssembly = Path.Combine(hostFxrRoot, hostFxrVersion.OriginalValue, hostFxrLibName); - return NativeLibrary.Load(hostFxrAssembly); + string hostFxrAssembly = Path.Combine(hostFxrAssemblyDirectory.OriginalValue, Path.ChangeExtension(hostFxrLibName, libExtension)); + + if (File.Exists(hostFxrAssembly)) + { + return NativeLibrary.TryLoad(hostFxrAssembly, out IntPtr handle) ? handle : IntPtr.Zero; + } } } - string error = $".NET SDK cannot be resolved, because {hostFxrLibName} cannot be found inside {hostFxrRoot}." + - Environment.NewLine + - $"This might indicate a corrupted SDK installation on the machine."; - - throw new InvalidOperationException(error); + return IntPtr.Zero; } private static string SdkResolutionExceptionMessage(string methodName) => $"Failed to find all versions of .NET Core MSBuild. Call to {methodName}. There may be more details in stderr."; - + /// /// Determines the directory location of the SDK accounting for /// global.json and multi-level lookup policy. @@ -176,7 +169,7 @@ private static IntPtr HostFxrResolver(Assembly assembly, string libraryName) private static string? GetSdkFromGlobalSettings(string workingDirectory) { string? resolvedSdk = null; - int rc = NativeMethods.hostfxr_resolve_sdk2(exe_dir: DotnetPath.Value, working_dir: workingDirectory, flags: 0, result: (key, value) => + int rc = NativeMethods.hostfxr_resolve_sdk2(exe_dir: s_dotnetPath.Value, working_dir: workingDirectory, flags: 0, result: (key, value) => { if (key == NativeMethods.hostfxr_resolve_sdk2_result_key_t.resolved_sdk_dir) { @@ -184,12 +177,9 @@ private static IntPtr HostFxrResolver(Assembly assembly, string libraryName) } }); - if (rc != 0) - { - throw new InvalidOperationException(SdkResolutionExceptionMessage(nameof(NativeMethods.hostfxr_resolve_sdk2))); - } - - return resolvedSdk; + return rc != 0 + ? throw new InvalidOperationException(SdkResolutionExceptionMessage(nameof(NativeMethods.hostfxr_resolve_sdk2))) + : resolvedSdk; } private static string ResolveDotnetPath() @@ -199,53 +189,27 @@ private static string ResolveDotnetPath() if (string.IsNullOrEmpty(dotnetPath)) { string? dotnetExePath = GetCurrentProcessPath(); - var isRunFromDotnetExecutable = !string.IsNullOrEmpty(dotnetExePath) - && Path.GetFileName(dotnetExePath).Equals(ExeName, StringComparison.OrdinalIgnoreCase); - - if (isRunFromDotnetExecutable) - { - dotnetPath = Path.GetDirectoryName(dotnetExePath); - } - else - { - // DOTNET_HOST_PATH is pointing to the file, DOTNET_ROOT is the path of the folder - string? hostPath = Environment.GetEnvironmentVariable("DOTNET_HOST_PATH"); - if (!string.IsNullOrEmpty(hostPath) && File.Exists(hostPath)) - { - if (!IsWindows) - { - hostPath = realpath(hostPath) ?? hostPath; - } - - dotnetPath = Path.GetDirectoryName(hostPath); - if (dotnetPath is not null) - { - // don't overwrite DOTNET_HOST_PATH, if we use it. - return dotnetPath; - } - } + bool isRunFromDotnetExecutable = !string.IsNullOrEmpty(dotnetExePath) + && Path.GetFileName(dotnetExePath).Equals(s_exeName, StringComparison.InvariantCultureIgnoreCase); - dotnetPath = FindDotnetPathFromEnvVariable("DOTNET_MSBUILD_SDK_RESOLVER_CLI_DIR") + dotnetPath = isRunFromDotnetExecutable + ? Path.GetDirectoryName(dotnetExePath) + : FindDotnetPathFromEnvVariable("DOTNET_HOST_PATH") + ?? FindDotnetPathFromEnvVariable("DOTNET_MSBUILD_SDK_RESOLVER_CLI_DIR") ?? GetDotnetPathFromPATH(); - } } - if (string.IsNullOrEmpty(dotnetPath)) - { - throw new InvalidOperationException("Could not find the dotnet executable. Is it set on the DOTNET_ROOT?"); - } - - SetEnvironmentVariableIfEmpty("DOTNET_HOST_PATH", Path.Combine(dotnetPath, ExeName)); - - return dotnetPath; + return string.IsNullOrEmpty(dotnetPath) + ? throw new InvalidOperationException("Could not find the dotnet executable. Is it set on the DOTNET_ROOT?") + : dotnetPath; } private static string? GetDotnetPathFromROOT() { // 32-bit architecture has (x86) suffix string envVarName = (IntPtr.Size == 4) ? "DOTNET_ROOT(x86)" : "DOTNET_ROOT"; - var dotnetPath = FindDotnetPathFromEnvVariable(envVarName); - + string? dotnetPath = FindDotnetPathFromEnvVariable(envVarName); + return dotnetPath; } @@ -260,7 +224,7 @@ private static string ResolveDotnetPath() // https://github.com/dotnet/designs/blob/main/accepted/2021/install-location-per-architecture.md // This could be done using the nethost library, but this is currently shipped as metadata package (Microsoft.NETCore.DotNetAppHost) and requires the customers // to specify for resolving runtime assembly. - var paths = Environment.GetEnvironmentVariable("PATH")?.Split(Path.PathSeparator) ?? Array.Empty(); + string[] paths = Environment.GetEnvironmentVariable("PATH")?.Split(Path.PathSeparator) ?? Array.Empty(); foreach (string dir in paths) { string? filePath = ValidatePath(dir); @@ -280,15 +244,12 @@ private static string ResolveDotnetPath() private static string[] GetAllAvailableSDKs() { string[]? resolvedPaths = null; - int rc = NativeMethods.hostfxr_get_available_sdks(exe_dir: DotnetPath.Value, result: (key, value) => resolvedPaths = value); + int rc = NativeMethods.hostfxr_get_available_sdks(exe_dir: s_dotnetPath.Value, result: (key, value) => resolvedPaths = value); // Errors are automatically printed to stderr. We should not continue to try to output anything if we failed. - if (rc != 0) - { - throw new InvalidOperationException(SdkResolutionExceptionMessage(nameof(NativeMethods.hostfxr_get_available_sdks))); - } - - return resolvedPaths ?? Array.Empty(); + return rc != 0 + ? throw new InvalidOperationException(SdkResolutionExceptionMessage(nameof(NativeMethods.hostfxr_get_available_sdks))) + : resolvedPaths ?? Array.Empty(); } /// @@ -307,24 +268,16 @@ private static string[] GetAllAvailableSDKs() private static string? FindDotnetPathFromEnvVariable(string environmentVariable) { string? dotnetPath = Environment.GetEnvironmentVariable(environmentVariable); - - return string.IsNullOrEmpty(dotnetPath) ? null : ValidatePath(dotnetPath); - } - private static void SetEnvironmentVariableIfEmpty(string name, string value) - { - if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable(name))) - { - Environment.SetEnvironmentVariable(name, value); - } + return string.IsNullOrEmpty(dotnetPath) ? null : ValidatePath(dotnetPath); } private static string? ValidatePath(string dotnetPath) { - string fullPathToDotnetFromRoot = Path.Combine(dotnetPath, ExeName); + string fullPathToDotnetFromRoot = Path.Combine(dotnetPath, s_exeName); if (File.Exists(fullPathToDotnetFromRoot)) { - if (!IsWindows) + if (!s_isWindows) { fullPathToDotnetFromRoot = realpath(fullPathToDotnetFromRoot) ?? fullPathToDotnetFromRoot; return File.Exists(fullPathToDotnetFromRoot) ? Path.GetDirectoryName(fullPathToDotnetFromRoot) : null; diff --git a/src/MSBuildLocator/MSBuildLocator.cs b/src/MSBuildLocator/MSBuildLocator.cs index ab435c86..2f99b347 100644 --- a/src/MSBuildLocator/MSBuildLocator.cs +++ b/src/MSBuildLocator/MSBuildLocator.cs @@ -60,10 +60,7 @@ public static class MSBuildLocator /// Only includes Visual Studio 2017 (v15.0) and higher. /// /// Enumeration of all Visual Studio instances detected on the machine. - public static IEnumerable QueryVisualStudioInstances() - { - return QueryVisualStudioInstances(VisualStudioInstanceQueryOptions.Default); - } + public static IEnumerable QueryVisualStudioInstances() => QueryVisualStudioInstances(VisualStudioInstanceQueryOptions.Default); /// /// Query for Visual Studio instances matching the given options. @@ -74,17 +71,11 @@ public static IEnumerable QueryVisualStudioInstances() /// Query options for Visual Studio instances. /// Enumeration of Visual Studio instances detected on the machine. public static IEnumerable QueryVisualStudioInstances( - VisualStudioInstanceQueryOptions options) - { - return QueryVisualStudioInstances(GetInstances(options), options); - } + VisualStudioInstanceQueryOptions options) => QueryVisualStudioInstances(GetInstances(options), options); internal static IEnumerable QueryVisualStudioInstances( IEnumerable instances, - VisualStudioInstanceQueryOptions options) - { - return instances.Where(i => options.DiscoveryTypes.HasFlag(i.DiscoveryType)); - } + VisualStudioInstanceQueryOptions options) => instances.Where(i => options.DiscoveryTypes.HasFlag(i.DiscoveryType)); /// /// Discover instances of Visual Studio and register the first one. See . @@ -95,7 +86,7 @@ public static VisualStudioInstance RegisterDefaults() VisualStudioInstance instance = GetInstances(VisualStudioInstanceQueryOptions.Default).FirstOrDefault(); if (instance == null) { - var error = "No instances of MSBuild could be detected." + + string error = "No instances of MSBuild could be detected." + Environment.NewLine + $"Try calling {nameof(RegisterInstance)} or {nameof(RegisterMSBuildPath)} to manually register one."; @@ -143,17 +134,14 @@ public static void RegisterInstance(VisualStudioInstance instance) /// Add assembly resolution for Microsoft.Build core dlls in the current AppDomain from the specified /// path. /// - /// + /// /// Path to the directory containing a deployment of MSBuild binaries. /// A minimal MSBuild deployment would be the publish result of the Microsoft.Build.Runtime package. /// /// In order to restore and build real projects, one needs a deployment that contains the rest of the toolchain (nuget, compilers, etc.). /// Such deployments can be found in installations such as Visual Studio or dotnet CLI. /// - public static void RegisterMSBuildPath(string msbuildPath) - { - RegisterMSBuildPath(new string[] { msbuildPath }); - } + public static void RegisterMSBuildPath(string msBuildPath) => RegisterMSBuildPath(new string[] { msBuildPath }); /// /// Add assembly resolution for Microsoft.Build core dlls in the current AppDomain from the specified @@ -178,7 +166,7 @@ public static void RegisterMSBuildPath(string[] msbuildSearchPaths) { if (string.IsNullOrWhiteSpace(msbuildSearchPaths[i])) { - nullOrWhiteSpaceExceptions.Add(new ArgumentException($"Value at position {i+1} may not be null or whitespace", nameof(msbuildSearchPaths))); + nullOrWhiteSpaceExceptions.Add(new ArgumentException($"Value at position {i + 1} may not be null or whitespace", nameof(msbuildSearchPaths))); } } if (nullOrWhiteSpaceExceptions.Count > 0) @@ -194,9 +182,9 @@ public static void RegisterMSBuildPath(string[] msbuildSearchPaths) if (!CanRegister) { - var loadedAssemblyList = string.Join(Environment.NewLine, LoadedMsBuildAssemblies.Select(a => a.GetName())); + string loadedAssemblyList = string.Join(Environment.NewLine, LoadedMsBuildAssemblies.Select(a => a.GetName())); - var error = $"{typeof(MSBuildLocator)}.{nameof(RegisterInstance)} was called, but MSBuild assemblies were already loaded." + + string error = $"{typeof(MSBuildLocator)}.{nameof(RegisterInstance)} was called, but MSBuild assemblies were already loaded." + Environment.NewLine + $"Ensure that {nameof(RegisterInstance)} is called before any method that directly references types in the Microsoft.Build namespace has been called." + Environment.NewLine + @@ -218,17 +206,17 @@ public static void RegisterMSBuildPath(string[] msbuildSearchPaths) // This overrides the latter assumption to let it find the right MSBuild. foreach (string path in msbuildSearchPaths) { - string msbuildExe = Path.Combine(path, "MSBuild.exe"); - if (File.Exists(msbuildExe)) + string msBuildExe = Path.Combine(path, "MSBuild.exe"); + if (File.Exists(msBuildExe)) { - FileVersionInfo ver = FileVersionInfo.GetVersionInfo(msbuildExe); + FileVersionInfo ver = FileVersionInfo.GetVersionInfo(msBuildExe); if (ver.FileMajorPart < 17 || (ver.FileMajorPart == 17 && ver.FileMinorPart < 1)) { - if (Path.GetDirectoryName(msbuildExe).EndsWith(@"\amd64", StringComparison.OrdinalIgnoreCase)) + if (Path.GetDirectoryName(msBuildExe).EndsWith(@"\amd64", StringComparison.OrdinalIgnoreCase)) { - msbuildExe = Path.Combine(path.Substring(0, path.Length - 6), "MSBuild.exe"); + msBuildExe = Path.Combine(path.Substring(0, path.Length - 6), "MSBuild.exe"); } - Environment.SetEnvironmentVariable("MSBUILD_EXE_PATH", msbuildExe); + Environment.SetEnvironmentVariable("MSBUILD_EXE_PATH", msBuildExe); } break; } @@ -245,10 +233,7 @@ public static void RegisterMSBuildPath(string[] msbuildSearchPaths) AppDomain.CurrentDomain.AssemblyResolve += s_registeredHandler; #else - s_registeredHandler = (_, assemblyName) => - { - return TryLoadAssembly(assemblyName); - }; + s_registeredHandler = (_, assemblyName) => TryLoadAssembly(assemblyName); AssemblyLoadContext.Default.Resolving += s_registeredHandler; #endif @@ -267,9 +252,9 @@ Assembly TryLoadAssembly(AssemblyName assemblyName) // Look in the MSBuild folder for any unresolved reference. It may be a dependency // of MSBuild or a task. - foreach (string msbuildPath in msbuildSearchPaths) + foreach (string msBuildPath in msbuildSearchPaths) { - string targetAssembly = Path.Combine(msbuildPath, assemblyName.Name + ".dll"); + string targetAssembly = Path.Combine(msBuildPath, assemblyName.Name + ".dll"); if (File.Exists(targetAssembly)) { assembly = Assembly.LoadFrom(targetAssembly); @@ -310,7 +295,7 @@ private static void ApplyDotNetSdkEnvironmentVariables(string dotNetSdkPath) [MSBuildSDKsPath] = Path.Combine(dotNetSdkPath, "Sdks") }; - foreach (var kvp in variables) + foreach (KeyValuePair kvp in variables) { Environment.SetEnvironmentVariable(kvp.Key, kvp.Value); } @@ -325,16 +310,16 @@ private static bool IsMSBuildAssembly(AssemblyName assemblyName) return false; } - var publicKeyToken = assemblyName.GetPublicKeyToken(); + byte[] publicKeyToken = assemblyName.GetPublicKeyToken(); if (publicKeyToken == null || publicKeyToken.Length == 0) { return false; } - var sb = new StringBuilder(); - foreach (var b in publicKeyToken) + StringBuilder sb = new StringBuilder(); + foreach (byte b in publicKeyToken) { - sb.Append($"{b:x2}"); + _ = sb.Append($"{b:x2}"); } return sb.ToString().Equals(MSBuildPublicKeyToken, StringComparison.OrdinalIgnoreCase); @@ -343,45 +328,50 @@ private static bool IsMSBuildAssembly(AssemblyName assemblyName) private static IEnumerable GetInstances(VisualStudioInstanceQueryOptions options) { #if NET46 - var devConsole = GetDevConsoleInstance(); + VisualStudioInstance devConsole = GetDevConsoleInstance(); if (devConsole != null) + { yield return devConsole; + } - #if FEATURE_VISUALSTUDIOSETUP - foreach (var instance in VisualStudioLocationHelper.GetInstances()) +#if FEATURE_VISUALSTUDIOSETUP + foreach (VisualStudioInstance instance in VisualStudioLocationHelper.GetInstances()) + { yield return instance; - #endif + } +#endif #endif #if NETCOREAPP - foreach (var dotnetSdk in DotNetSdkLocationHelper.GetInstances(options.WorkingDirectory)) + foreach (VisualStudioInstance dotnetSdk in DotNetSdkLocationHelper.GetInstances(options.WorkingDirectory)) + { yield return dotnetSdk; + } #endif } #if NET46 private static VisualStudioInstance GetDevConsoleInstance() { - var path = Environment.GetEnvironmentVariable("VSINSTALLDIR"); + string path = Environment.GetEnvironmentVariable("VSINSTALLDIR"); if (!string.IsNullOrEmpty(path)) { - var versionString = Environment.GetEnvironmentVariable("VSCMD_VER"); - Version version; - Version.TryParse(versionString, out version); + string versionString = Environment.GetEnvironmentVariable("VSCMD_VER"); + _ = Version.TryParse(versionString, out Version version); if (version == null && versionString?.Contains('-') == true) { versionString = versionString.Substring(0, versionString.IndexOf('-')); - Version.TryParse(versionString, out version); + _ = Version.TryParse(versionString, out version); } if (version == null) { versionString = Environment.GetEnvironmentVariable("VisualStudioVersion"); - Version.TryParse(versionString, out version); + _ = Version.TryParse(versionString, out version); } - if(version != null) + if (version != null) { return new VisualStudioInstance("DEVCONSOLE", path, version, DiscoveryType.DeveloperConsole); } diff --git a/src/MSBuildLocator/NativeMethods.cs b/src/MSBuildLocator/NativeMethods.cs index 3a2b2371..b42277ce 100644 --- a/src/MSBuildLocator/NativeMethods.cs +++ b/src/MSBuildLocator/NativeMethods.cs @@ -8,8 +8,6 @@ namespace Microsoft.Build.Locator { internal class NativeMethods { - internal const string HostFxrName = "hostfxr"; - internal enum hostfxr_resolve_sdk2_flags_t { disallow_prerelease = 0x1, @@ -32,14 +30,14 @@ internal delegate void hostfxr_get_available_sdks_result_fn( [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] string[] value); - [DllImport(HostFxrName, CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + [DllImport("hostfxr", CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] internal static extern int hostfxr_resolve_sdk2( string exe_dir, string working_dir, hostfxr_resolve_sdk2_flags_t flags, hostfxr_resolve_sdk2_result_fn result); - [DllImport(HostFxrName, CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + [DllImport("hostfxr", CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] internal static extern int hostfxr_get_available_sdks(string exe_dir, hostfxr_get_available_sdks_result_fn result); [DllImport("libc", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] diff --git a/src/MSBuildLocator/Properties/AssemblyInfo.cs b/src/MSBuildLocator/Properties/AssemblyInfo.cs index 074d5ee6..889c4d27 100644 --- a/src/MSBuildLocator/Properties/AssemblyInfo.cs +++ b/src/MSBuildLocator/Properties/AssemblyInfo.cs @@ -3,4 +3,4 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("Microsoft.Build.Locator.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100adad416086fc859fe034cc45ba1ae17cbcaa8d471c5884ef08af87f7cd4e1bbb663e1387f24bbc479d913f125643e19d6da998b0b31a2979abcd36a858756676a65f36b27d6b6a3fd330e20ed5f73da134938bbbf90276d1fcf2f887e44e0eca4d767e147d95220433e5a4e14dcb6e6cea955e360a53642985b407a63e21f0ab")] \ No newline at end of file +[assembly: InternalsVisibleTo("Microsoft.Build.Locator.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100adad416086fc859fe034cc45ba1ae17cbcaa8d471c5884ef08af87f7cd4e1bbb663e1387f24bbc479d913f125643e19d6da998b0b31a2979abcd36a858756676a65f36b27d6b6a3fd330e20ed5f73da134938bbbf90276d1fcf2f887e44e0eca4d767e147d95220433e5a4e14dcb6e6cea955e360a53642985b407a63e21f0ab")] diff --git a/src/MSBuildLocator/Utils/SemanticVersion.cs b/src/MSBuildLocator/Utils/SemanticVersion.cs index 13ef2501..971adc0b 100644 --- a/src/MSBuildLocator/Utils/SemanticVersion.cs +++ b/src/MSBuildLocator/Utils/SemanticVersion.cs @@ -5,13 +5,12 @@ using System.Collections.Generic; using System.Linq; -namespace Microsoft.Build.Locator +namespace Microsoft.Build.Locator.Utils { internal class SemanticVersion : IComparable { private readonly IEnumerable _releaseLabels; - private Version _version; - private string _originalValue; + private readonly Version _version; public SemanticVersion(Version version, IEnumerable releaseLabels, string originalValue) { @@ -22,7 +21,7 @@ public SemanticVersion(Version version, IEnumerable releaseLabels, strin _releaseLabels = releaseLabels.ToArray(); } - _originalValue = originalValue; + OriginalValue = originalValue; } /// @@ -45,12 +44,12 @@ public SemanticVersion(Version version, IEnumerable releaseLabels, strin /// public IEnumerable ReleaseLabels => _releaseLabels ?? Enumerable.Empty(); - public string OriginalValue => _originalValue; + public string OriginalValue { get; } /// /// The full pre-release label for the version. /// - public string Release => _releaseLabels != null ? String.Join(".", _releaseLabels) : String.Empty; + public string Release => _releaseLabels != null ? string.Join(".", _releaseLabels) : string.Empty; /// /// True if pre-release labels exist for the version. @@ -61,8 +60,8 @@ public bool IsPrerelease { if (ReleaseLabels != null) { - var enumerator = ReleaseLabels.GetEnumerator(); - return (enumerator.MoveNext() && !String.IsNullOrEmpty(enumerator.Current)); + IEnumerator enumerator = ReleaseLabels.GetEnumerator(); + return enumerator.MoveNext() && !string.IsNullOrEmpty(enumerator.Current); } return false; @@ -72,9 +71,6 @@ public bool IsPrerelease /// /// Compare versions. /// - public int CompareTo(SemanticVersion other) - { - return VersionComparer.Compare(this, other); - } + public int CompareTo(SemanticVersion other) => VersionComparer.Compare(this, other); } } diff --git a/src/MSBuildLocator/Utils/SemanticVersionParser.cs b/src/MSBuildLocator/Utils/SemanticVersionParser.cs index 9bea0025..808117a0 100644 --- a/src/MSBuildLocator/Utils/SemanticVersionParser.cs +++ b/src/MSBuildLocator/Utils/SemanticVersionParser.cs @@ -5,6 +5,7 @@ using System; using System.Linq; +using Microsoft.Build.Locator.Utils; namespace Microsoft.Build.Locator { @@ -24,7 +25,7 @@ public static bool TryParse(string value, out SemanticVersion version) if (value != null) { - var (versionString, releaseLabels) = ParseSections(value); + (string versionString, string[] releaseLabels) = ParseSections(value); if (Version.TryParse(versionString, out Version systemVersion)) { @@ -85,7 +86,7 @@ private static bool IsValidPart(char[] chars, bool allowLeadingZeros) // 0 is fine, but 00 is not. // 0A counts as an alpha numeric string where zeros are not counted - if (!allowLeadingZeros && chars.Length > 1 && chars[0] == '0' && chars.All(c => Char.IsDigit(c))) + if (!allowLeadingZeros && chars.Length > 1 && chars[0] == '0' && chars.All(c => char.IsDigit(c))) { // no leading zeros in labels allowed result = false; @@ -166,4 +167,4 @@ private static Version NormalizeVersionValue(Version version) } } } -#endif \ No newline at end of file +#endif diff --git a/src/MSBuildLocator/Utils/VersionComparer.cs b/src/MSBuildLocator/Utils/VersionComparer.cs index 2197cb25..1d3707fa 100644 --- a/src/MSBuildLocator/Utils/VersionComparer.cs +++ b/src/MSBuildLocator/Utils/VersionComparer.cs @@ -4,9 +4,9 @@ using System; using System.Collections.Generic; -namespace Microsoft.Build.Locator +namespace Microsoft.Build.Locator.Utils { - internal static class VersionComparer + internal sealed class VersionComparer { /// /// Determines if both versions are equal. @@ -18,17 +18,17 @@ internal static class VersionComparer /// public static int Compare(SemanticVersion x, SemanticVersion y) { - if (Object.ReferenceEquals(x, y)) + if (ReferenceEquals(x, y)) { return 0; } - if (Object.ReferenceEquals(y, null)) + if (y is null) { return 1; } - if (Object.ReferenceEquals(x, null)) + if (x is null) { return -1; } @@ -40,7 +40,7 @@ public static int Compare(SemanticVersion x, SemanticVersion y) if (result != 0) { return result; - } + } result = x.Minor.CompareTo(y.Minor); if (result != 0) @@ -96,20 +96,20 @@ private static int CompareReleaseLabels(IEnumerable version1, IEnumerabl if (!aExists && bExists) { return -1; - } + } if (aExists && !bExists) { return 1; } - + result = CompareRelease(a.Current, b.Current); if (result != 0) { return result; } - + aExists = a.MoveNext(); bExists = b.MoveNext(); } @@ -126,8 +126,8 @@ private static int CompareRelease(string version1, string version2) int result; // check if the identifiers are numeric - bool v1IsNumeric = Int32.TryParse(version1, out int version1Num); - bool v2IsNumeric = Int32.TryParse(version2, out int version2Num); + bool v1IsNumeric = int.TryParse(version1, out int version1Num); + bool v2IsNumeric = int.TryParse(version2, out int version2Num); // if both are numeric compare them as numbers if (v1IsNumeric && v2IsNumeric) diff --git a/src/MSBuildLocator/VisualStudioInstance.cs b/src/MSBuildLocator/VisualStudioInstance.cs index e8185dec..34c58cd1 100644 --- a/src/MSBuildLocator/VisualStudioInstance.cs +++ b/src/MSBuildLocator/VisualStudioInstance.cs @@ -60,4 +60,4 @@ internal VisualStudioInstance(string name, string path, Version version, Discove /// public DiscoveryType DiscoveryType { get; } } -} \ No newline at end of file +} diff --git a/src/MSBuildLocator/VisualStudioLocationHelper.cs b/src/MSBuildLocator/VisualStudioLocationHelper.cs index 87d1c22d..7197b0f8 100644 --- a/src/MSBuildLocator/VisualStudioLocationHelper.cs +++ b/src/MSBuildLocator/VisualStudioLocationHelper.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. + // Taken from https://github.com/Microsoft/msbuild/blob/6851538897f5d7b08024a6d8435bc44be5869e53/src/Shared/VisualStudioLocationHelper.cs #if FEATURE_VISUALSTUDIOSETUP @@ -18,7 +19,7 @@ namespace Microsoft.Build.Locator /// internal class VisualStudioLocationHelper { - private const int REGDB_E_CLASSNOTREG = unchecked((int) 0x80040154); + private const int REGDB_E_CLASSNOTREG = unchecked((int)0x80040154); /// /// Query the Visual Studio setup API to get instances of Visual Studio installed @@ -32,8 +33,8 @@ internal static IList GetInstances() try { // This code is not obvious. See the sample (link above) for reference. - var query = (ISetupConfiguration2) GetQuery(); - var e = query.EnumAllInstances(); + var query = (ISetupConfiguration2)GetQuery(); + IEnumSetupInstances e = query.EnumAllInstances(); int fetched; var instances = new ISetupInstance[1]; @@ -41,13 +42,18 @@ internal static IList GetInstances() { // Call e.Next to query for the next instance (single item or nothing returned). e.Next(1, instances, out fetched); - if (fetched <= 0) continue; + if (fetched <= 0) + { + continue; + } - var instance = (ISetupInstance2) instances[0]; + var instance = (ISetupInstance2)instances[0]; InstanceState state = instance.GetState(); if (!Version.TryParse(instance.GetInstallationVersion(), out Version version)) + { continue; + } // If the install was complete and a valid version, consider it. if (state == InstanceState.Complete || @@ -96,19 +102,15 @@ private static ISetupConfiguration GetQuery() catch (COMException ex) when (ex.ErrorCode == REGDB_E_CLASSNOTREG) { // Try to get the class object using app-local call. - ISetupConfiguration query; - var result = GetSetupConfiguration(out query, IntPtr.Zero); - - if (result < 0) - throw new COMException($"Failed to get {nameof(query)}", result); + int result = GetSetupConfiguration(out ISetupConfiguration query, IntPtr.Zero); - return query; + return result < 0 ? throw new COMException($"Failed to get {nameof(query)}", result) : query; } } [DllImport("Microsoft.VisualStudio.Setup.Configuration.Native.dll", ExactSpelling = true, PreserveSig = true)] private static extern int GetSetupConfiguration( - [MarshalAs(UnmanagedType.Interface)] [Out] out ISetupConfiguration configuration, + [MarshalAs(UnmanagedType.Interface)][Out] out ISetupConfiguration configuration, IntPtr reserved); } } From f8548cfaff08699d214f5bb67e4fd7cb1f3fe9fb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 31 Aug 2023 10:41:31 +0200 Subject: [PATCH 3/8] Bump Microsoft.NET.Test.Sdk from 17.7.1 to 17.7.2 (#241) Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.7.1 to 17.7.2. - [Release notes](https://github.com/microsoft/vstest/releases) - [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md) - [Commits](https://github.com/microsoft/vstest/compare/v17.7.1...v17.7.2) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/MSBuildLocator.Tests/Microsoft.Build.Locator.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MSBuildLocator.Tests/Microsoft.Build.Locator.Tests.csproj b/src/MSBuildLocator.Tests/Microsoft.Build.Locator.Tests.csproj index 4114e127..4c8d0abf 100644 --- a/src/MSBuildLocator.Tests/Microsoft.Build.Locator.Tests.csproj +++ b/src/MSBuildLocator.Tests/Microsoft.Build.Locator.Tests.csproj @@ -8,7 +8,7 @@ - + From 6478808299f5452cf24056eb5b65c63932f5eda6 Mon Sep 17 00:00:00 2001 From: YuliiaKovalova <95473390+YuliiaKovalova@users.noreply.github.com> Date: Wed, 13 Sep 2023 16:46:55 +0200 Subject: [PATCH 4/8] update warning presence in Microsoft.Build.Locator.targets (#233) --- samples/BuilderApp/BuilderApp.csproj | 2 ++ src/MSBuildLocator/build/Microsoft.Build.Locator.targets | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/samples/BuilderApp/BuilderApp.csproj b/samples/BuilderApp/BuilderApp.csproj index 9035d7de..e15c0d4e 100644 --- a/samples/BuilderApp/BuilderApp.csproj +++ b/samples/BuilderApp/BuilderApp.csproj @@ -28,4 +28,6 @@ Not necessary if you use the package! --> + + diff --git a/src/MSBuildLocator/build/Microsoft.Build.Locator.targets b/src/MSBuildLocator/build/Microsoft.Build.Locator.targets index f17cd107..a117f136 100644 --- a/src/MSBuildLocator/build/Microsoft.Build.Locator.targets +++ b/src/MSBuildLocator/build/Microsoft.Build.Locator.targets @@ -3,8 +3,7 @@ Date: Fri, 15 Sep 2023 10:08:11 +0200 Subject: [PATCH 5/8] add probing of all available path candidates + return missed DOTNET_HOST_PATH set change (#243) --- README.md | 14 ++ src/MSBuildLocator/DotNetSdkLocationHelper.cs | 122 ++++++++++++------ 2 files changed, 100 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index bd817e0c..6f9a58c2 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,20 @@ That additional build logic is distributed with Visual Studio, with Visual Studi Loading MSBuild from Visual Studio also ensures that your application gets the same view of projects as `MSBuild.exe`, `dotnet build`, or Visual Studio, including bug fixes, feature additions, and performance improvements that may come from a newer MSBuild release. +## How Locator searches for .NET SDK? + +MSBuild.Locator searches for the locally installed SDK based on the following priority: + +1. DOTNET_ROOT +2. Current process path if MSBuild.Locator is called from dotnet.exe +3. DOTNET_HOST_PATH +4. DOTNET_MSBUILD_SDK_RESOLVER_CLI_DIR +5. PATH + +Note that probing stops when the first dotnet executable is found among the listed variables. + +Documentation describing the definition of these variables can be found here: [.NET Environment Variables](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-environment-variables). + ## Documentation Documentation is located on the official Microsoft documentation site: [Use Microsoft.Build.Locator](https://docs.microsoft.com/visualstudio/msbuild/updating-an-existing-application#use-microsoftbuildlocator). diff --git a/src/MSBuildLocator/DotNetSdkLocationHelper.cs b/src/MSBuildLocator/DotNetSdkLocationHelper.cs index b12ca840..f8da9c3d 100644 --- a/src/MSBuildLocator/DotNetSdkLocationHelper.cs +++ b/src/MSBuildLocator/DotNetSdkLocationHelper.cs @@ -22,7 +22,7 @@ internal static class DotNetSdkLocationHelper private static readonly Regex s_versionRegex = new(@"^(\d+)\.(\d+)\.(\d+)", RegexOptions.Multiline); private static readonly bool s_isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); private static readonly string s_exeName = s_isWindows ? "dotnet.exe" : "dotnet"; - private static readonly Lazy s_dotnetPath = new(() => ResolveDotnetPath()); + private static readonly Lazy> s_dotnetPathCandidates = new(() => ResolveDotnetPathCandidates()); public static VisualStudioInstance? GetInstance(string dotNetSdkPath) { @@ -131,28 +131,44 @@ private static void ModifyUnmanagedDllResolver(Action resol private static IntPtr HostFxrResolver(Assembly assembly, string libraryName) { + // Library name for libhostfxr string hostFxrLibName = "libhostfxr"; + // Library extension for the current platform string libExtension = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "dylib" : "so"; + // If the requested library name is not libhostfxr, return IntPtr.Zero if (!hostFxrLibName.Equals(libraryName)) { return IntPtr.Zero; } - string hostFxrRoot = Path.Combine(s_dotnetPath.Value, "host", "fxr"); - if (Directory.Exists(hostFxrRoot)) + // Get the dotnet path candidates + foreach (string dotnetPath in s_dotnetPathCandidates.Value) { - // Load hostfxr from the highest version, because it should be backward-compatible - SemanticVersion? hostFxrAssemblyDirectory = Directory.GetDirectories(hostFxrRoot) - .Max(str => SemanticVersionParser.TryParse(str, out SemanticVersion? version) ? version : null); + string hostFxrRoot = Path.Combine(dotnetPath, "host", "fxr"); - if (hostFxrAssemblyDirectory != null && !string.IsNullOrEmpty(hostFxrAssemblyDirectory.OriginalValue)) + // Check if the host/fxr directory exists + if (Directory.Exists(hostFxrRoot)) { - string hostFxrAssembly = Path.Combine(hostFxrAssemblyDirectory.OriginalValue, Path.ChangeExtension(hostFxrLibName, libExtension)); - - if (File.Exists(hostFxrAssembly)) + // Get a list of hostfxr assembly directories (e.g., 6.0.3, 7.0.1-preview.2.4) + IList hostFxrAssemblyDirs = Directory.GetDirectories(hostFxrRoot) + .Select(path => SemanticVersionParser.TryParse(Path.GetFileName(path), out SemanticVersion? version) ? version : null) + .Where(v => v != null) + .Cast() + .OrderByDescending(v => v) + .ToList(); + + foreach (SemanticVersion hostFxrDir in hostFxrAssemblyDirs) { - return NativeLibrary.TryLoad(hostFxrAssembly, out IntPtr handle) ? handle : IntPtr.Zero; + string hostFxrAssemblyPath = Path.Combine(hostFxrRoot, hostFxrDir.OriginalValue, $"{hostFxrLibName}.{libExtension}"); + + if (File.Exists(hostFxrAssemblyPath)) + { + if (NativeLibrary.TryLoad(hostFxrAssemblyPath, out IntPtr handle)) + { + return handle; + } + } } } } @@ -169,39 +185,59 @@ private static IntPtr HostFxrResolver(Assembly assembly, string libraryName) private static string? GetSdkFromGlobalSettings(string workingDirectory) { string? resolvedSdk = null; - int rc = NativeMethods.hostfxr_resolve_sdk2(exe_dir: s_dotnetPath.Value, working_dir: workingDirectory, flags: 0, result: (key, value) => + foreach (string dotnetPath in s_dotnetPathCandidates.Value) { - if (key == NativeMethods.hostfxr_resolve_sdk2_result_key_t.resolved_sdk_dir) + int rc = NativeMethods.hostfxr_resolve_sdk2(exe_dir: dotnetPath, working_dir: workingDirectory, flags: 0, result: (key, value) => { - resolvedSdk = value; - } - }); + if (key == NativeMethods.hostfxr_resolve_sdk2_result_key_t.resolved_sdk_dir) + { + resolvedSdk = value; + } + }); - return rc != 0 + if (rc == 0) + { + SetEnvironmentVariableIfEmpty("DOTNET_HOST_PATH", dotnetPath); + return resolvedSdk; + } + } + + return string.IsNullOrEmpty(resolvedSdk) ? throw new InvalidOperationException(SdkResolutionExceptionMessage(nameof(NativeMethods.hostfxr_resolve_sdk2))) : resolvedSdk; } - private static string ResolveDotnetPath() + private static IList ResolveDotnetPathCandidates() { - string? dotnetPath = GetDotnetPathFromROOT(); + var pathCandidates = new List(); + AddIfValid(GetDotnetPathFromROOT()); - if (string.IsNullOrEmpty(dotnetPath)) + string? dotnetExePath = GetCurrentProcessPath(); + bool isRunFromDotnetExecutable = !string.IsNullOrEmpty(dotnetExePath) + && Path.GetFileName(dotnetExePath).Equals(s_exeName, StringComparison.InvariantCultureIgnoreCase); + + if (isRunFromDotnetExecutable) { - string? dotnetExePath = GetCurrentProcessPath(); - bool isRunFromDotnetExecutable = !string.IsNullOrEmpty(dotnetExePath) - && Path.GetFileName(dotnetExePath).Equals(s_exeName, StringComparison.InvariantCultureIgnoreCase); - - dotnetPath = isRunFromDotnetExecutable - ? Path.GetDirectoryName(dotnetExePath) - : FindDotnetPathFromEnvVariable("DOTNET_HOST_PATH") - ?? FindDotnetPathFromEnvVariable("DOTNET_MSBUILD_SDK_RESOLVER_CLI_DIR") - ?? GetDotnetPathFromPATH(); + AddIfValid(Path.GetDirectoryName(dotnetExePath)); } - return string.IsNullOrEmpty(dotnetPath) - ? throw new InvalidOperationException("Could not find the dotnet executable. Is it set on the DOTNET_ROOT?") - : dotnetPath; + AddIfValid(FindDotnetPathFromEnvVariable("DOTNET_HOST_PATH")); + AddIfValid(FindDotnetPathFromEnvVariable("DOTNET_MSBUILD_SDK_RESOLVER_CLI_DIR")); + AddIfValid(GetDotnetPathFromPATH()); + + return pathCandidates.Count == 0 + ? throw new InvalidOperationException("Path to dotnet executable is not set. " + + "The probed variables are: DOTNET_ROOT, DOTNET_HOST_PATH, DOTNET_MSBUILD_SDK_RESOLVER_CLI_DIR and PATH. " + + "Make sure, that at least one of the listed variables points to the existing dotnet executable.") + : pathCandidates; + + void AddIfValid(string? path) + { + if (!string.IsNullOrEmpty(path)) + { + pathCandidates.Add(path); + } + } } private static string? GetDotnetPathFromROOT() @@ -244,12 +280,18 @@ private static string ResolveDotnetPath() private static string[] GetAllAvailableSDKs() { string[]? resolvedPaths = null; - int rc = NativeMethods.hostfxr_get_available_sdks(exe_dir: s_dotnetPath.Value, result: (key, value) => resolvedPaths = value); + foreach (string dotnetPath in s_dotnetPathCandidates.Value) + { + int rc = NativeMethods.hostfxr_get_available_sdks(exe_dir: dotnetPath, result: (key, value) => resolvedPaths = value); + + if (rc == 0 && resolvedPaths != null && resolvedPaths.Length > 0) + { + break; + } + } // Errors are automatically printed to stderr. We should not continue to try to output anything if we failed. - return rc != 0 - ? throw new InvalidOperationException(SdkResolutionExceptionMessage(nameof(NativeMethods.hostfxr_get_available_sdks))) - : resolvedPaths ?? Array.Empty(); + return resolvedPaths ?? throw new InvalidOperationException(SdkResolutionExceptionMessage(nameof(NativeMethods.hostfxr_get_available_sdks))); } /// @@ -265,6 +307,14 @@ private static string[] GetAllAvailableSDKs() return result; } + private static void SetEnvironmentVariableIfEmpty(string name, string value) + { + if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable(name))) + { + Environment.SetEnvironmentVariable(name, value); + } + } + private static string? FindDotnetPathFromEnvVariable(string environmentVariable) { string? dotnetPath = Environment.GetEnvironmentVariable(environmentVariable); From 8ff2415834e7ed930a4a06400aa2e0b7f38d3615 Mon Sep 17 00:00:00 2001 From: YuliiaKovalova <95473390+YuliiaKovalova@users.noreply.github.com> Date: Tue, 19 Sep 2023 10:12:41 +0200 Subject: [PATCH 6/8] Revert code style changes (#244) * revert code stype changes * apply changes for probing dotnet paths * fix review comment --- .editorconfig | 400 ------------------ MSBuildLocator.sln | 1 - samples/BuilderApp/Program.cs | 45 +- src/MSBuildLocator.Tests/QueryOptionsTests.cs | 4 +- .../SemanticVersionParserTests.cs | 59 ++- src/MSBuildLocator/DiscoveryType.cs | 2 +- src/MSBuildLocator/DotNetSdkLocationHelper.cs | 114 ++--- src/MSBuildLocator/MSBuildLocator.cs | 90 ++-- src/MSBuildLocator/NativeMethods.cs | 6 +- src/MSBuildLocator/Properties/AssemblyInfo.cs | 2 +- src/MSBuildLocator/Utils/SemanticVersion.cs | 20 +- .../Utils/SemanticVersionParser.cs | 7 +- src/MSBuildLocator/Utils/VersionComparer.cs | 22 +- src/MSBuildLocator/VisualStudioInstance.cs | 2 +- .../VisualStudioLocationHelper.cs | 26 +- 15 files changed, 210 insertions(+), 590 deletions(-) delete mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 1111154c..00000000 --- a/.editorconfig +++ /dev/null @@ -1,400 +0,0 @@ -# editorconfig.org - -# top-most EditorConfig file -root = true - -# Default settings: -# A newline ending every file -# Use 4 spaces as indentation -[*] -insert_final_newline = true -indent_style = space -indent_size = 4 -trim_trailing_whitespace = true - -[project.json] -indent_size = 2 - -# C# files -[*.cs] -# New line preferences -csharp_new_line_before_open_brace = all -csharp_new_line_before_else = true -csharp_new_line_before_catch = true -csharp_new_line_before_finally = true -csharp_new_line_before_members_in_object_initializers = true -csharp_new_line_before_members_in_anonymous_types = true -csharp_new_line_between_query_expression_clauses = true - -# Indentation preferences -csharp_indent_block_contents = true -csharp_indent_braces = false -csharp_indent_case_contents = true -csharp_indent_case_contents_when_block = true -csharp_indent_switch_labels = true -csharp_indent_labels = one_less_than_current - -# Modifier preferences -csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion - -# avoid this. unless absolutely necessary -dotnet_style_qualification_for_field = false:suggestion -dotnet_style_qualification_for_property = false:suggestion -dotnet_style_qualification_for_method = false:suggestion -dotnet_style_qualification_for_event = false:suggestion - -# Types: use keywords instead of BCL types, and permit var only when the type is clear -csharp_style_var_for_built_in_types = false:suggestion -csharp_style_var_when_type_is_apparent = false:none -csharp_style_var_elsewhere = false:suggestion -dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion -dotnet_style_predefined_type_for_member_access = true:suggestion - -# name all constant fields using PascalCase -dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion -dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields -dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style -dotnet_naming_symbols.constant_fields.applicable_kinds = field -dotnet_naming_symbols.constant_fields.required_modifiers = const -dotnet_naming_style.pascal_case_style.capitalization = pascal_case - -# static fields should have s_ prefix -dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion -dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields -dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style -dotnet_naming_symbols.static_fields.applicable_kinds = field -dotnet_naming_symbols.static_fields.required_modifiers = static -dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected -dotnet_naming_style.static_prefix_style.required_prefix = s_ -dotnet_naming_style.static_prefix_style.capitalization = camel_case - -# internal and private fields should be _camelCase -dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion -dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields -dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style -dotnet_naming_symbols.private_internal_fields.applicable_kinds = field -dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal -dotnet_naming_style.camel_case_underscore_style.required_prefix = _ -dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case - -# Code style defaults -csharp_using_directive_placement = outside_namespace:suggestion -dotnet_sort_system_directives_first = true -csharp_prefer_braces = true:silent -csharp_preserve_single_line_blocks = true:none -csharp_preserve_single_line_statements = false:none -csharp_prefer_static_local_function = true:suggestion -csharp_prefer_simple_using_statement = false:none -csharp_style_prefer_switch_expression = true:suggestion - -# Code quality -dotnet_style_readonly_field = true:suggestion -dotnet_code_quality_unused_parameters = non_public:suggestion - -# Expression-level preferences -dotnet_style_object_initializer = true:suggestion -dotnet_style_collection_initializer = true:suggestion -dotnet_style_explicit_tuple_names = true:suggestion -dotnet_style_coalesce_expression = true:suggestion -dotnet_style_null_propagation = true:suggestion -dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion -dotnet_style_prefer_inferred_tuple_names = true:suggestion -dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion -dotnet_style_prefer_auto_properties = true:suggestion -dotnet_style_prefer_conditional_expression_over_assignment = true:silent -dotnet_style_prefer_conditional_expression_over_return = true:silent -csharp_prefer_simple_default_expression = true:suggestion - -# Expression-bodied members -csharp_style_expression_bodied_methods = true:silent -csharp_style_expression_bodied_constructors = true:silent -csharp_style_expression_bodied_operators = true:silent -csharp_style_expression_bodied_properties = true:silent -csharp_style_expression_bodied_indexers = true:silent -csharp_style_expression_bodied_accessors = true:silent -csharp_style_expression_bodied_lambdas = true:silent -csharp_style_expression_bodied_local_functions = true:silent - -# Pattern matching -csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion -csharp_style_pattern_matching_over_as_with_null_check = true:suggestion -csharp_style_inlined_variable_declaration = true:suggestion - -# Null checking preferences -csharp_style_throw_expression = true:suggestion -csharp_style_conditional_delegate_call = true:suggestion - -# Other features -csharp_style_prefer_index_operator = false:none -csharp_style_prefer_range_operator = false:none -csharp_style_pattern_local_over_anonymous_function = false:none - -# Space preferences -csharp_space_after_cast = false -csharp_space_after_colon_in_inheritance_clause = true -csharp_space_after_comma = true -csharp_space_after_dot = false -csharp_space_after_keywords_in_control_flow_statements = true -csharp_space_after_semicolon_in_for_statement = true -csharp_space_around_binary_operators = before_and_after -csharp_space_around_declaration_statements = do_not_ignore -csharp_space_before_colon_in_inheritance_clause = true -csharp_space_before_comma = false -csharp_space_before_dot = false -csharp_space_before_open_square_brackets = false -csharp_space_before_semicolon_in_for_statement = false -csharp_space_between_empty_square_brackets = false -csharp_space_between_method_call_empty_parameter_list_parentheses = false -csharp_space_between_method_call_name_and_opening_parenthesis = false -csharp_space_between_method_call_parameter_list_parentheses = false -csharp_space_between_method_declaration_empty_parameter_list_parentheses = false -csharp_space_between_method_declaration_name_and_open_parenthesis = false -csharp_space_between_method_declaration_parameter_list_parentheses = false -csharp_space_between_parentheses = false -csharp_space_between_square_brackets = false - -# Analyzers -dotnet_code_quality.ca1802.api_surface = private, internal -dotnet_code_quality.ca2208.api_surface = public - -# CA1852: Seal internal types -dotnet_diagnostic.ca1852.severity = warning - -# RS0037: Enable tracking of nullability of reference types in the declared API -# Our API is not annotated but new classes get nullable enabled so disable this. -# We'd be happy if everything was annotated and this could be removed. -dotnet_diagnostic.RS0037.severity = none - -# License header -file_header_template = Copyright (c) Microsoft. All rights reserved.\nLicensed under the MIT license. See LICENSE file in the project root for full license information. - -# C++ Files -[*.{cpp,h,in}] -curly_bracket_next_line = true -indent_brace_style = Allman - -# Xml project files -[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}] -indent_size = 2 - -[*.{csproj,vbproj,proj,nativeproj,locproj}] -charset = utf-8 - -# Xml build files -[*.builds] -indent_size = 2 - -# Xml files -[*.{xml,stylecop,resx,ruleset}] -indent_size = 2 - -# Xml config files -[*.{props,targets,config,nuspec}] -indent_size = 2 - -# YAML config files -[*.{yml,yaml}] -indent_size = 2 - -# Shell scripts -[*.sh] -end_of_line = lf -[*.{cmd, bat}] -end_of_line = crlf - -[src/**/*.{cs,vb}] -# Code style checks -dotnet_analyzer_diagnostic.category-Style.severity = warning - -# Cast is redundant -dotnet_diagnostic.IDE0004.severity = suggestion - -# IDE0005: Remove unnecessary usings/imports -dotnet_diagnostic.IDE0005.severity = none - -# Use explicit type instead of 'var' -dotnet_diagnostic.IDE0008.severity = suggestion - -# Populate switch -dotnet_diagnostic.IDE0010.severity = suggestion - -# Null check can be simplified -dotnet_diagnostic.IDE0016.severity = suggestion - -# Object initialization can be simplified -dotnet_diagnostic.IDE0017.severity = suggestion - -# Variable declaration can be inlined -dotnet_diagnostic.IDE0018.severity = suggestion - -# Use pattern matching -dotnet_diagnostic.IDE0019.severity = suggestion -dotnet_diagnostic.IDE0020.severity = suggestion - -# Use expression body for constructor -dotnet_diagnostic.IDE0021.severity = suggestion - -# Use expression body for method -dotnet_diagnostic.IDE0022.severity = suggestion - -# Use expression body for conversion operator -dotnet_diagnostic.IDE0023.severity = suggestion - -# Use block body for operator -dotnet_diagnostic.IDE0024.severity = suggestion - -# Use expression body for property -dotnet_diagnostic.IDE0025.severity = suggestion - -# Use expression body for indexer -dotnet_diagnostic.IDE0026.severity = suggestion - -# Use expression body for accessor -dotnet_diagnostic.IDE0027.severity = suggestion - -# Collection initialization can be simplified -dotnet_diagnostic.IDE0028.severity = suggestion - -# Null check can be simplified -dotnet_diagnostic.IDE0031.severity = suggestion - -# Use auto property -dotnet_diagnostic.IDE0032.severity = suggestion - -# 'default' expression can be simplified -dotnet_diagnostic.IDE0034.severity = suggestion - -# Member name can be simplified -dotnet_diagnostic.IDE0037.severity = suggestion - -# Use local function -dotnet_diagnostic.IDE0039.severity = suggestion - -# Null check can be simplified -dotnet_diagnostic.IDE0041.severity = suggestion - -# Variable declaration can be deconstructed -dotnet_diagnostic.IDE0042.severity = suggestion - -# Made field readonly -dotnet_diagnostic.IDE0044.severity = suggestion - -# 'if' statement can be simplified -dotnet_diagnostic.IDE0045.severity = suggestion -dotnet_diagnostic.IDE0046.severity = suggestion - -# Parentheses can be removed -dotnet_diagnostic.IDE0047.severity = suggestion - -# Parentheses should be added for clarity -dotnet_diagnostic.IDE0048.severity = suggestion - -# Member name can be simplified -dotnet_diagnostic.IDE0049.severity = suggestion - -# Use compound assignment -dotnet_diagnostic.IDE0054.severity = suggestion - -# Fix formatting -dotnet_diagnostic.IDE0055.severity = suggestion - -# Indexing can be simplified -dotnet_diagnostic.IDE0056.severity = suggestion - -# Slice can be simplified -dotnet_diagnostic.IDE0057.severity = suggestion - -# Expression value is never used -dotnet_diagnostic.IDE0058.severity = suggestion - -# Unnecessary assignment of a value -dotnet_diagnostic.IDE0059.severity = suggestion - -# Remove unused parameter -dotnet_diagnostic.IDE0060.severity = suggestion - -# Use expression body for a local function -dotnet_diagnostic.IDE0061.severity = suggestion - -# Local function can be made static -dotnet_diagnostic.IDE0062.severity = suggestion - -# Using directives must be placed outside of a namespace declaration -dotnet_diagnostic.IDE0065.severity = suggestion - -# Use 'switch' expression -dotnet_diagnostic.IDE0066.severity = suggestion - -# 'GetHashCode' implementation can be simplified -dotnet_diagnostic.IDE0070.severity = suggestion - -# Interpolation can be simplified -dotnet_diagnostic.IDE0071.severity = suggestion - -# Populate switch -dotnet_diagnostic.IDE0072.severity = suggestion - -# Use compound assignment -dotnet_diagnostic.IDE0074.severity = suggestion - -# Conditional expression can be simplified -dotnet_diagnostic.IDE0075.severity = suggestion - -# Use pattern matching -dotnet_diagnostic.IDE0078.severity = suggestion -dotnet_diagnostic.IDE0083.severity = suggestion - -# 'typeof' can be converted to 'nameof' -dotnet_diagnostic.IDE0082.severity = suggestion - -# 'new' expression can be simplified -dotnet_diagnostic.IDE0090.severity = suggestion - -# Simplify LINQ expression -dotnet_diagnostic.IDE0120.severity = suggestion - -# namespace does not match folder structure -dotnet_diagnostic.IDE0130.severity = suggestion - -# Null check can be clarified -dotnet_diagnostic.IDE0150.severity = suggestion - -# Convert to block scoped namespaces -dotnet_diagnostic.IDE0160.severity = suggestion - -# Simplify property pattern -dotnet_diagnostic.IDE0170.severity = suggestion - -# Use tuple to swap values -dotnet_diagnostic.IDE0180.severity = suggestion - -# Use tuple to swap values -dotnet_diagnostic.IDE0180.severity = suggestion - -# Lambda expression can be removed -dotnet_diagnostic.IDE0200.severity = suggestion - -# Convert to top-level statements -dotnet_diagnostic.IDE0210.severity = suggestion - -# 'foreach' statement implicitly converts -dotnet_diagnostic.IDE0220.severity = suggestion - -# Use UTF-8 string literal -dotnet_diagnostic.IDE0230.severity = suggestion - -# Nullable directives -dotnet_diagnostic.IDE0240.severity = suggestion -dotnet_diagnostic.IDE0241.severity = suggestion - -# Struct can be made 'readonly' -dotnet_diagnostic.IDE0250.severity = suggestion - -# Struct methods can be made 'readonly' -dotnet_diagnostic.IDE0251.severity = suggestion - -# Null check can be simplified -dotnet_diagnostic.IDE0270.severity = suggestion - -# naming rule violation -dotnet_diagnostic.IDE1006.severity = suggestion diff --git a/MSBuildLocator.sln b/MSBuildLocator.sln index 68f161de..4540e62d 100644 --- a/MSBuildLocator.sln +++ b/MSBuildLocator.sln @@ -13,7 +13,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject Directory.Build.props = Directory.Build.props version.json = version.json - .editorconfig = .editorconfig EndProjectSection EndProject Global diff --git a/samples/BuilderApp/Program.cs b/samples/BuilderApp/Program.cs index 56d8ccc4..0ec2f0bc 100644 --- a/samples/BuilderApp/Program.cs +++ b/samples/BuilderApp/Program.cs @@ -6,7 +6,6 @@ using System.Diagnostics; using System.IO; using System.Linq; -using System.Reflection; using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; using Microsoft.Build.Framework; @@ -14,37 +13,37 @@ namespace BuilderApp { - internal sealed class Program + internal class Program { private static void Main(string[] args) { Header(); - string projectFilePath = Args(args); + var projectFilePath = Args(args); // Before we can build we need to resolve MSBuild assemblies. We could: // 1) Use defaults and call: MSBuildLocator.RegisterDefaults(); // 2) Do something fancier and ask the user. As an example we'll do that. var instances = MSBuildLocator.QueryVisualStudioInstances().ToList(); - (VisualStudioInstance VSInstance, string MSBuildPath) = AskWhichMSBuildToUse(instances); + var msbuildDeploymentToUse = AskWhichMSBuildToUse(instances); // Calling Register methods will subscribe to AssemblyResolve event. After this we can // safely call code that use MSBuild types (in the Builder class). - if (VSInstance != null) + if (msbuildDeploymentToUse.VSInstance != null) { - Console.WriteLine($"Using MSBuild from VS Instance: {VSInstance.Name} - {VSInstance.Version}"); + Console.WriteLine($"Using MSBuild from VS Instance: {msbuildDeploymentToUse.VSInstance.Name} - {msbuildDeploymentToUse.VSInstance.Version}"); Console.WriteLine(); - MSBuildLocator.RegisterInstance(VSInstance); + MSBuildLocator.RegisterInstance(msbuildDeploymentToUse.VSInstance); } else { - Console.WriteLine($"Using MSBuild from path: {MSBuildPath}"); + Console.WriteLine($"Using MSBuild from path: {msbuildDeploymentToUse.MSBuildPath}"); Console.WriteLine(); - MSBuildLocator.RegisterMSBuildPath(MSBuildPath); + MSBuildLocator.RegisterMSBuildPath(msbuildDeploymentToUse.MSBuildPath); } - bool result = Builder.Build(projectFilePath); + var result = new Builder().Build(projectFilePath); Console.WriteLine(); Console.ForegroundColor = result ? ConsoleColor.Green : ConsoleColor.Red; @@ -60,10 +59,10 @@ private static (VisualStudioInstance VSInstance, string MSBuildPath) AskWhichMSB } Console.WriteLine($"0) Custom path"); - for (int i = 1; i <= instances.Count; i++) + for (var i = 1; i <= instances.Count; i++) { - VisualStudioInstance instance = instances[i - 1]; - string recommended = string.Empty; + var instance = instances[i - 1]; + var recommended = string.Empty; // The dev console is probably always the right choice because the user explicitly opened // one associated with a Visual Studio install. It will always be first in the list. @@ -75,27 +74,27 @@ private static (VisualStudioInstance VSInstance, string MSBuildPath) AskWhichMSB Console.WriteLine(); Console.WriteLine("Select an instance of MSBuild: "); - string answer = Console.ReadLine(); + var answer = Console.ReadLine(); if (int.TryParse(answer, out int instanceChoice) && instanceChoice >= 0 && instanceChoice <= instances.Count) { if (instanceChoice == 0) { Console.WriteLine("Input path to MSBuild deployment:"); - string msBuildPath = Console.ReadLine(); + var msbuildPath = Console.ReadLine(); - if (!Directory.Exists(msBuildPath)) + if (!Directory.Exists(msbuildPath)) { - Console.WriteLine($"Directory does not exist: {msBuildPath}"); + Console.WriteLine($"Directory does not exist: {msbuildPath}"); Environment.Exit(-1); } - return (null, msBuildPath); + return (null, msbuildPath); } else { - VisualStudioInstance instanceUsed = instances[instanceChoice - 1]; + var instanceUsed = instances[instanceChoice - 1]; return (instanceUsed, null); } } @@ -117,7 +116,7 @@ private static void Header() private static string Args(string[] args) { if (args.Length < 1 || !File.Exists(args[0])) Usage(); - string projectFilePath = args[0]; + var projectFilePath = args[0]; return projectFilePath; } @@ -140,9 +139,9 @@ private static void Usage() /// public class Builder { - public static bool Build(string projectFile) + public bool Build(string projectFile) { - Assembly assembly = typeof(Project).Assembly; + var assembly = typeof(Project).Assembly; FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(assembly.Location); Console.WriteLine(); @@ -155,7 +154,7 @@ public static bool Build(string projectFile) return project.Build(new Logger()); } - private sealed class Logger : ILogger + private class Logger : ILogger { public void Initialize(IEventSource eventSource) { diff --git a/src/MSBuildLocator.Tests/QueryOptionsTests.cs b/src/MSBuildLocator.Tests/QueryOptionsTests.cs index 842e116d..c9fb62ea 100644 --- a/src/MSBuildLocator.Tests/QueryOptionsTests.cs +++ b/src/MSBuildLocator.Tests/QueryOptionsTests.cs @@ -55,7 +55,7 @@ public void NoResultsTest() VerifyQueryResults(instances, DiscoveryType.DotNetSdk); } - private static void VerifyQueryResults(IEnumerable instances, DiscoveryType discoveryTypes, params string[] expectedInstanceNames) + private void VerifyQueryResults(IEnumerable instances, DiscoveryType discoveryTypes, params string[] expectedInstanceNames) { IEnumerable actual = MSBuildLocator.QueryVisualStudioInstances(instances, new VisualStudioInstanceQueryOptions { @@ -76,4 +76,4 @@ private static void VerifyQueryResults(IEnumerable instanc } } } -} +} \ No newline at end of file diff --git a/src/MSBuildLocator.Tests/SemanticVersionParserTests.cs b/src/MSBuildLocator.Tests/SemanticVersionParserTests.cs index 94f73db4..f9e57b3f 100644 --- a/src/MSBuildLocator.Tests/SemanticVersionParserTests.cs +++ b/src/MSBuildLocator.Tests/SemanticVersionParserTests.cs @@ -3,7 +3,6 @@ #if NETCOREAPP -using Microsoft.Build.Locator.Utils; using Shouldly; using System.Linq; using Xunit; @@ -15,61 +14,61 @@ public class SemanticVersionParserTests [Fact] public void TryParseTest_ReleaseVersion() { - string version = "7.0.333"; + var version = "7.0.333"; - bool isParsed = SemanticVersionParser.TryParse(version, out SemanticVersion parsedVersion); + var isParsed = SemanticVersionParser.TryParse(version, out var parsedVerion); - _ = parsedVersion.ShouldNotBeNull(); + parsedVerion.ShouldNotBeNull(); isParsed.ShouldBeTrue(); - parsedVersion.Major.ShouldBe(7); - parsedVersion.Minor.ShouldBe(0); - parsedVersion.Patch.ShouldBe(333); - parsedVersion.ReleaseLabels.ShouldBeEmpty(); + parsedVerion.Major.ShouldBe(7); + parsedVerion.Minor.ShouldBe(0); + parsedVerion.Patch.ShouldBe(333); + parsedVerion.ReleaseLabels.ShouldBeEmpty(); } [Fact] public void TryParseTest_PreviewVersion() { - string version = "8.0.0-preview.6.23329.7"; + var version = "8.0.0-preview.6.23329.7"; - bool isParsed = SemanticVersionParser.TryParse(version, out SemanticVersion parsedVersion); + var isParsed = SemanticVersionParser.TryParse(version, out var parsedVerion); - _ = parsedVersion.ShouldNotBeNull(); + parsedVerion.ShouldNotBeNull(); isParsed.ShouldBeTrue(); - parsedVersion.Major.ShouldBe(8); - parsedVersion.Minor.ShouldBe(0); - parsedVersion.Patch.ShouldBe(0); - parsedVersion.ReleaseLabels.ShouldBe(new[] { "preview", "6", "23329", "7" }); + parsedVerion.Major.ShouldBe(8); + parsedVerion.Minor.ShouldBe(0); + parsedVerion.Patch.ShouldBe(0); + parsedVerion.ReleaseLabels.ShouldBe(new[] { "preview", "6", "23329", "7" }); } [Fact] public void TryParseTest_InvalidInput_LeadingZero() { - string version = "0.0-preview.6"; + var version = "0.0-preview.6"; - bool isParsed = SemanticVersionParser.TryParse(version, out SemanticVersion parsedVersion); + var isParsed = SemanticVersionParser.TryParse(version, out var parsedVerion); - Assert.Null(parsedVersion); + Assert.Null(parsedVerion); isParsed.ShouldBeFalse(); } [Fact] public void TryParseTest_InvalidInput_FourPartsVersion() { - string version = "5.0.3.4"; + var version = "5.0.3.4"; - bool isParsed = SemanticVersionParser.TryParse(version, out SemanticVersion parsedVersion); + var isParsed = SemanticVersionParser.TryParse(version, out var parsedVerion); - Assert.Null(parsedVersion); + Assert.Null(parsedVerion); isParsed.ShouldBeFalse(); } [Fact] public void VersionSortingTest_WithPreview() { - string[] versions = new[] { "7.0.7", "8.0.0-preview.6.23329.7", "8.0.0-preview.3.23174.8" }; + var versions = new[] { "7.0.7", "8.0.0-preview.6.23329.7", "8.0.0-preview.3.23174.8" }; - SemanticVersion maxVersion = versions.Select(v => SemanticVersionParser.TryParse(v, out SemanticVersion parsedVersion) ? parsedVersion : null).Max(); + var maxVersion = versions.Select(v => SemanticVersionParser.TryParse(v, out var parsedVerion) ? parsedVerion : null).Max(); maxVersion.OriginalValue.ShouldBe("8.0.0-preview.6.23329.7"); } @@ -77,9 +76,9 @@ public void VersionSortingTest_WithPreview() [Fact] public void VersionSortingTest_ReleaseOnly() { - string[] versions = new[] { "7.0.7", "3.7.2", "10.0.0" }; + var versions = new[] { "7.0.7", "3.7.2", "10.0.0" }; - SemanticVersion maxVersion = versions.Max(v => SemanticVersionParser.TryParse(v, out SemanticVersion parsedVersion) ? parsedVersion : null); + var maxVersion = versions.Max(v => SemanticVersionParser.TryParse(v, out var parsedVerion) ? parsedVerion : null); maxVersion.OriginalValue.ShouldBe("10.0.0"); } @@ -87,9 +86,9 @@ public void VersionSortingTest_ReleaseOnly() [Fact] public void VersionSortingTest_WithInvalidFolderNames() { - string[] versions = new[] { "7.0.7", "3.7.2", "dummy", "5.7.8.9" }; + var versions = new[] { "7.0.7", "3.7.2", "dummy", "5.7.8.9" }; - SemanticVersion maxVersion = versions.Max(v => SemanticVersionParser.TryParse(v, out SemanticVersion parsedVersion) ? parsedVersion : null); + var maxVersion = versions.Max(v => SemanticVersionParser.TryParse(v, out var parsedVerion) ? parsedVerion : null); maxVersion.OriginalValue.ShouldBe("7.0.7"); } @@ -97,12 +96,12 @@ public void VersionSortingTest_WithInvalidFolderNames() [Fact] public void VersionSortingTest_WithAllInvalidFolderNames() { - string[] versions = new[] { "dummy", "5.7.8.9" }; + var versions = new[] { "dummy", "5.7.8.9" }; - SemanticVersion maxVersion = versions.Max(v => SemanticVersionParser.TryParse(v, out SemanticVersion parsedVersion) ? parsedVersion : null); + var maxVersion = versions.Max(v => SemanticVersionParser.TryParse(v, out var parsedVerion) ? parsedVerion : null); maxVersion.ShouldBeNull(); } } } -#endif +#endif \ No newline at end of file diff --git a/src/MSBuildLocator/DiscoveryType.cs b/src/MSBuildLocator/DiscoveryType.cs index 79bd4db9..7a4ad585 100644 --- a/src/MSBuildLocator/DiscoveryType.cs +++ b/src/MSBuildLocator/DiscoveryType.cs @@ -27,4 +27,4 @@ public enum DiscoveryType /// DotNetSdk = 4 } -} +} \ No newline at end of file diff --git a/src/MSBuildLocator/DotNetSdkLocationHelper.cs b/src/MSBuildLocator/DotNetSdkLocationHelper.cs index f8da9c3d..4d7cc0a3 100644 --- a/src/MSBuildLocator/DotNetSdkLocationHelper.cs +++ b/src/MSBuildLocator/DotNetSdkLocationHelper.cs @@ -6,12 +6,12 @@ using System; using System.Collections.Generic; using System.IO; +using System.IO.Enumeration; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using System.Runtime.Loader; using System.Text.RegularExpressions; -using Microsoft.Build.Locator.Utils; #nullable enable @@ -19,9 +19,9 @@ namespace Microsoft.Build.Locator { internal static class DotNetSdkLocationHelper { - private static readonly Regex s_versionRegex = new(@"^(\d+)\.(\d+)\.(\d+)", RegexOptions.Multiline); - private static readonly bool s_isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - private static readonly string s_exeName = s_isWindows ? "dotnet.exe" : "dotnet"; + private static readonly Regex VersionRegex = new Regex(@"^(\d+)\.(\d+)\.(\d+)", RegexOptions.Multiline); + private static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + private static readonly string ExeName = IsWindows ? "dotnet.exe" : "dotnet"; private static readonly Lazy> s_dotnetPathCandidates = new(() => ResolveDotnetPathCandidates()); public static VisualStudioInstance? GetInstance(string dotNetSdkPath) @@ -38,7 +38,7 @@ internal static class DotNetSdkLocationHelper } // Preview versions contain a hyphen after the numeric part of the version. Version.TryParse doesn't accept that. - Match versionMatch = s_versionRegex.Match(File.ReadAllText(versionPath)); + Match versionMatch = VersionRegex.Match(File.ReadAllText(versionPath)); if (!versionMatch.Success) { @@ -70,10 +70,10 @@ internal static class DotNetSdkLocationHelper } public static IEnumerable GetInstances(string workingDirectory) - { - foreach (string basePath in GetDotNetBasePaths(workingDirectory)) + { + foreach (var basePath in GetDotNetBasePaths(workingDirectory)) { - VisualStudioInstance? dotnetSdk = GetInstance(basePath); + var dotnetSdk = GetInstance(basePath); if (dotnetSdk != null) { yield return dotnetSdk; @@ -119,7 +119,7 @@ private static IEnumerable GetDotNetBasePaths(string workingDirectory) private static void ModifyUnmanagedDllResolver(Action resolverAction) { // For Windows hostfxr is loaded in the process. - if (!s_isWindows) + if (!IsWindows) { var loadContext = AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly()); if (loadContext != null) @@ -131,53 +131,53 @@ private static void ModifyUnmanagedDllResolver(Action resol private static IntPtr HostFxrResolver(Assembly assembly, string libraryName) { - // Library name for libhostfxr - string hostFxrLibName = "libhostfxr"; - // Library extension for the current platform - string libExtension = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "dylib" : "so"; - - // If the requested library name is not libhostfxr, return IntPtr.Zero - if (!hostFxrLibName.Equals(libraryName)) + // the DllImport hardcoded the name as hostfxr. + if (!libraryName.Equals(NativeMethods.HostFxrName, StringComparison.Ordinal)) { return IntPtr.Zero; } + string hostFxrLibName = + RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? + "hostfxr.dll" : + RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "libhostfxr.dylib" : "libhostfxr.so"; + string hostFxrRoot = string.Empty; + // Get the dotnet path candidates foreach (string dotnetPath in s_dotnetPathCandidates.Value) { - string hostFxrRoot = Path.Combine(dotnetPath, "host", "fxr"); - - // Check if the host/fxr directory exists + hostFxrRoot = Path.Combine(dotnetPath, "host", "fxr"); if (Directory.Exists(hostFxrRoot)) { - // Get a list of hostfxr assembly directories (e.g., 6.0.3, 7.0.1-preview.2.4) - IList hostFxrAssemblyDirs = Directory.GetDirectories(hostFxrRoot) - .Select(path => SemanticVersionParser.TryParse(Path.GetFileName(path), out SemanticVersion? version) ? version : null) - .Where(v => v != null) - .Cast() - .OrderByDescending(v => v) - .ToList(); - - foreach (SemanticVersion hostFxrDir in hostFxrAssemblyDirs) + var fileEnumerable = new FileSystemEnumerable( + directory: hostFxrRoot, + transform: static (ref FileSystemEntry entry) => SemanticVersionParser.TryParse(entry.FileName.ToString(), out var version) ? version : null) { - string hostFxrAssemblyPath = Path.Combine(hostFxrRoot, hostFxrDir.OriginalValue, $"{hostFxrLibName}.{libExtension}"); + ShouldIncludePredicate = static (ref FileSystemEntry entry) => entry.IsDirectory + }; - if (File.Exists(hostFxrAssemblyPath)) + var orderedVersions = fileEnumerable.Where(v => v != null).Select(v => v!).OrderByDescending(f => f).ToList(); + + foreach (SemanticVersion hostFxrVersion in orderedVersions) + { + string hostFxrAssembly = Path.Combine(hostFxrRoot, hostFxrVersion.OriginalValue, hostFxrLibName); + if (NativeLibrary.TryLoad(hostFxrAssembly, out IntPtr handle)) { - if (NativeLibrary.TryLoad(hostFxrAssemblyPath, out IntPtr handle)) - { - return handle; - } + return handle; } } } } - return IntPtr.Zero; + string error = $".NET SDK cannot be resolved, because {hostFxrLibName} cannot be found inside {hostFxrRoot}." + + Environment.NewLine + + $"This might indicate a corrupted SDK installation on the machine."; + + throw new InvalidOperationException(error); } private static string SdkResolutionExceptionMessage(string methodName) => $"Failed to find all versions of .NET Core MSBuild. Call to {methodName}. There may be more details in stderr."; - + /// /// Determines the directory location of the SDK accounting for /// global.json and multi-level lookup policy. @@ -197,11 +197,11 @@ private static IntPtr HostFxrResolver(Assembly assembly, string libraryName) if (rc == 0) { - SetEnvironmentVariableIfEmpty("DOTNET_HOST_PATH", dotnetPath); + SetEnvironmentVariableIfEmpty("DOTNET_HOST_PATH", Path.Combine(dotnetPath, ExeName)); return resolvedSdk; } } - + return string.IsNullOrEmpty(resolvedSdk) ? throw new InvalidOperationException(SdkResolutionExceptionMessage(nameof(NativeMethods.hostfxr_resolve_sdk2))) : resolvedSdk; @@ -214,14 +214,24 @@ private static IList ResolveDotnetPathCandidates() string? dotnetExePath = GetCurrentProcessPath(); bool isRunFromDotnetExecutable = !string.IsNullOrEmpty(dotnetExePath) - && Path.GetFileName(dotnetExePath).Equals(s_exeName, StringComparison.InvariantCultureIgnoreCase); + && Path.GetFileName(dotnetExePath).Equals(ExeName, StringComparison.InvariantCultureIgnoreCase); if (isRunFromDotnetExecutable) { AddIfValid(Path.GetDirectoryName(dotnetExePath)); } - AddIfValid(FindDotnetPathFromEnvVariable("DOTNET_HOST_PATH")); + string? hostPath = Environment.GetEnvironmentVariable("DOTNET_HOST_PATH"); + if (!string.IsNullOrEmpty(hostPath) && File.Exists(hostPath)) + { + if (!IsWindows) + { + hostPath = realpath(hostPath) ?? hostPath; + } + + AddIfValid(Path.GetDirectoryName(hostPath)); + } + AddIfValid(FindDotnetPathFromEnvVariable("DOTNET_MSBUILD_SDK_RESOLVER_CLI_DIR")); AddIfValid(GetDotnetPathFromPATH()); @@ -244,8 +254,8 @@ void AddIfValid(string? path) { // 32-bit architecture has (x86) suffix string envVarName = (IntPtr.Size == 4) ? "DOTNET_ROOT(x86)" : "DOTNET_ROOT"; - string? dotnetPath = FindDotnetPathFromEnvVariable(envVarName); - + var dotnetPath = FindDotnetPathFromEnvVariable(envVarName); + return dotnetPath; } @@ -260,7 +270,7 @@ void AddIfValid(string? path) // https://github.com/dotnet/designs/blob/main/accepted/2021/install-location-per-architecture.md // This could be done using the nethost library, but this is currently shipped as metadata package (Microsoft.NETCore.DotNetAppHost) and requires the customers // to specify for resolving runtime assembly. - string[] paths = Environment.GetEnvironmentVariable("PATH")?.Split(Path.PathSeparator) ?? Array.Empty(); + var paths = Environment.GetEnvironmentVariable("PATH")?.Split(Path.PathSeparator) ?? Array.Empty(); foreach (string dir in paths) { string? filePath = ValidatePath(dir); @@ -307,6 +317,13 @@ private static string[] GetAllAvailableSDKs() return result; } + private static string? FindDotnetPathFromEnvVariable(string environmentVariable) + { + string? dotnetPath = Environment.GetEnvironmentVariable(environmentVariable); + + return string.IsNullOrEmpty(dotnetPath) ? null : ValidatePath(dotnetPath); + } + private static void SetEnvironmentVariableIfEmpty(string name, string value) { if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable(name))) @@ -315,19 +332,12 @@ private static void SetEnvironmentVariableIfEmpty(string name, string value) } } - private static string? FindDotnetPathFromEnvVariable(string environmentVariable) - { - string? dotnetPath = Environment.GetEnvironmentVariable(environmentVariable); - - return string.IsNullOrEmpty(dotnetPath) ? null : ValidatePath(dotnetPath); - } - private static string? ValidatePath(string dotnetPath) { - string fullPathToDotnetFromRoot = Path.Combine(dotnetPath, s_exeName); + string fullPathToDotnetFromRoot = Path.Combine(dotnetPath, ExeName); if (File.Exists(fullPathToDotnetFromRoot)) { - if (!s_isWindows) + if (!IsWindows) { fullPathToDotnetFromRoot = realpath(fullPathToDotnetFromRoot) ?? fullPathToDotnetFromRoot; return File.Exists(fullPathToDotnetFromRoot) ? Path.GetDirectoryName(fullPathToDotnetFromRoot) : null; diff --git a/src/MSBuildLocator/MSBuildLocator.cs b/src/MSBuildLocator/MSBuildLocator.cs index 2f99b347..ab435c86 100644 --- a/src/MSBuildLocator/MSBuildLocator.cs +++ b/src/MSBuildLocator/MSBuildLocator.cs @@ -60,7 +60,10 @@ public static class MSBuildLocator /// Only includes Visual Studio 2017 (v15.0) and higher. /// /// Enumeration of all Visual Studio instances detected on the machine. - public static IEnumerable QueryVisualStudioInstances() => QueryVisualStudioInstances(VisualStudioInstanceQueryOptions.Default); + public static IEnumerable QueryVisualStudioInstances() + { + return QueryVisualStudioInstances(VisualStudioInstanceQueryOptions.Default); + } /// /// Query for Visual Studio instances matching the given options. @@ -71,11 +74,17 @@ public static class MSBuildLocator /// Query options for Visual Studio instances. /// Enumeration of Visual Studio instances detected on the machine. public static IEnumerable QueryVisualStudioInstances( - VisualStudioInstanceQueryOptions options) => QueryVisualStudioInstances(GetInstances(options), options); + VisualStudioInstanceQueryOptions options) + { + return QueryVisualStudioInstances(GetInstances(options), options); + } internal static IEnumerable QueryVisualStudioInstances( IEnumerable instances, - VisualStudioInstanceQueryOptions options) => instances.Where(i => options.DiscoveryTypes.HasFlag(i.DiscoveryType)); + VisualStudioInstanceQueryOptions options) + { + return instances.Where(i => options.DiscoveryTypes.HasFlag(i.DiscoveryType)); + } /// /// Discover instances of Visual Studio and register the first one. See . @@ -86,7 +95,7 @@ public static VisualStudioInstance RegisterDefaults() VisualStudioInstance instance = GetInstances(VisualStudioInstanceQueryOptions.Default).FirstOrDefault(); if (instance == null) { - string error = "No instances of MSBuild could be detected." + + var error = "No instances of MSBuild could be detected." + Environment.NewLine + $"Try calling {nameof(RegisterInstance)} or {nameof(RegisterMSBuildPath)} to manually register one."; @@ -134,14 +143,17 @@ public static void RegisterInstance(VisualStudioInstance instance) /// Add assembly resolution for Microsoft.Build core dlls in the current AppDomain from the specified /// path. /// - /// + /// /// Path to the directory containing a deployment of MSBuild binaries. /// A minimal MSBuild deployment would be the publish result of the Microsoft.Build.Runtime package. /// /// In order to restore and build real projects, one needs a deployment that contains the rest of the toolchain (nuget, compilers, etc.). /// Such deployments can be found in installations such as Visual Studio or dotnet CLI. /// - public static void RegisterMSBuildPath(string msBuildPath) => RegisterMSBuildPath(new string[] { msBuildPath }); + public static void RegisterMSBuildPath(string msbuildPath) + { + RegisterMSBuildPath(new string[] { msbuildPath }); + } /// /// Add assembly resolution for Microsoft.Build core dlls in the current AppDomain from the specified @@ -166,7 +178,7 @@ public static void RegisterMSBuildPath(string[] msbuildSearchPaths) { if (string.IsNullOrWhiteSpace(msbuildSearchPaths[i])) { - nullOrWhiteSpaceExceptions.Add(new ArgumentException($"Value at position {i + 1} may not be null or whitespace", nameof(msbuildSearchPaths))); + nullOrWhiteSpaceExceptions.Add(new ArgumentException($"Value at position {i+1} may not be null or whitespace", nameof(msbuildSearchPaths))); } } if (nullOrWhiteSpaceExceptions.Count > 0) @@ -182,9 +194,9 @@ public static void RegisterMSBuildPath(string[] msbuildSearchPaths) if (!CanRegister) { - string loadedAssemblyList = string.Join(Environment.NewLine, LoadedMsBuildAssemblies.Select(a => a.GetName())); + var loadedAssemblyList = string.Join(Environment.NewLine, LoadedMsBuildAssemblies.Select(a => a.GetName())); - string error = $"{typeof(MSBuildLocator)}.{nameof(RegisterInstance)} was called, but MSBuild assemblies were already loaded." + + var error = $"{typeof(MSBuildLocator)}.{nameof(RegisterInstance)} was called, but MSBuild assemblies were already loaded." + Environment.NewLine + $"Ensure that {nameof(RegisterInstance)} is called before any method that directly references types in the Microsoft.Build namespace has been called." + Environment.NewLine + @@ -206,17 +218,17 @@ public static void RegisterMSBuildPath(string[] msbuildSearchPaths) // This overrides the latter assumption to let it find the right MSBuild. foreach (string path in msbuildSearchPaths) { - string msBuildExe = Path.Combine(path, "MSBuild.exe"); - if (File.Exists(msBuildExe)) + string msbuildExe = Path.Combine(path, "MSBuild.exe"); + if (File.Exists(msbuildExe)) { - FileVersionInfo ver = FileVersionInfo.GetVersionInfo(msBuildExe); + FileVersionInfo ver = FileVersionInfo.GetVersionInfo(msbuildExe); if (ver.FileMajorPart < 17 || (ver.FileMajorPart == 17 && ver.FileMinorPart < 1)) { - if (Path.GetDirectoryName(msBuildExe).EndsWith(@"\amd64", StringComparison.OrdinalIgnoreCase)) + if (Path.GetDirectoryName(msbuildExe).EndsWith(@"\amd64", StringComparison.OrdinalIgnoreCase)) { - msBuildExe = Path.Combine(path.Substring(0, path.Length - 6), "MSBuild.exe"); + msbuildExe = Path.Combine(path.Substring(0, path.Length - 6), "MSBuild.exe"); } - Environment.SetEnvironmentVariable("MSBUILD_EXE_PATH", msBuildExe); + Environment.SetEnvironmentVariable("MSBUILD_EXE_PATH", msbuildExe); } break; } @@ -233,7 +245,10 @@ public static void RegisterMSBuildPath(string[] msbuildSearchPaths) AppDomain.CurrentDomain.AssemblyResolve += s_registeredHandler; #else - s_registeredHandler = (_, assemblyName) => TryLoadAssembly(assemblyName); + s_registeredHandler = (_, assemblyName) => + { + return TryLoadAssembly(assemblyName); + }; AssemblyLoadContext.Default.Resolving += s_registeredHandler; #endif @@ -252,9 +267,9 @@ Assembly TryLoadAssembly(AssemblyName assemblyName) // Look in the MSBuild folder for any unresolved reference. It may be a dependency // of MSBuild or a task. - foreach (string msBuildPath in msbuildSearchPaths) + foreach (string msbuildPath in msbuildSearchPaths) { - string targetAssembly = Path.Combine(msBuildPath, assemblyName.Name + ".dll"); + string targetAssembly = Path.Combine(msbuildPath, assemblyName.Name + ".dll"); if (File.Exists(targetAssembly)) { assembly = Assembly.LoadFrom(targetAssembly); @@ -295,7 +310,7 @@ private static void ApplyDotNetSdkEnvironmentVariables(string dotNetSdkPath) [MSBuildSDKsPath] = Path.Combine(dotNetSdkPath, "Sdks") }; - foreach (KeyValuePair kvp in variables) + foreach (var kvp in variables) { Environment.SetEnvironmentVariable(kvp.Key, kvp.Value); } @@ -310,16 +325,16 @@ private static bool IsMSBuildAssembly(AssemblyName assemblyName) return false; } - byte[] publicKeyToken = assemblyName.GetPublicKeyToken(); + var publicKeyToken = assemblyName.GetPublicKeyToken(); if (publicKeyToken == null || publicKeyToken.Length == 0) { return false; } - StringBuilder sb = new StringBuilder(); - foreach (byte b in publicKeyToken) + var sb = new StringBuilder(); + foreach (var b in publicKeyToken) { - _ = sb.Append($"{b:x2}"); + sb.Append($"{b:x2}"); } return sb.ToString().Equals(MSBuildPublicKeyToken, StringComparison.OrdinalIgnoreCase); @@ -328,50 +343,45 @@ private static bool IsMSBuildAssembly(AssemblyName assemblyName) private static IEnumerable GetInstances(VisualStudioInstanceQueryOptions options) { #if NET46 - VisualStudioInstance devConsole = GetDevConsoleInstance(); + var devConsole = GetDevConsoleInstance(); if (devConsole != null) - { yield return devConsole; - } -#if FEATURE_VISUALSTUDIOSETUP - foreach (VisualStudioInstance instance in VisualStudioLocationHelper.GetInstances()) - { + #if FEATURE_VISUALSTUDIOSETUP + foreach (var instance in VisualStudioLocationHelper.GetInstances()) yield return instance; - } -#endif + #endif #endif #if NETCOREAPP - foreach (VisualStudioInstance dotnetSdk in DotNetSdkLocationHelper.GetInstances(options.WorkingDirectory)) - { + foreach (var dotnetSdk in DotNetSdkLocationHelper.GetInstances(options.WorkingDirectory)) yield return dotnetSdk; - } #endif } #if NET46 private static VisualStudioInstance GetDevConsoleInstance() { - string path = Environment.GetEnvironmentVariable("VSINSTALLDIR"); + var path = Environment.GetEnvironmentVariable("VSINSTALLDIR"); if (!string.IsNullOrEmpty(path)) { - string versionString = Environment.GetEnvironmentVariable("VSCMD_VER"); - _ = Version.TryParse(versionString, out Version version); + var versionString = Environment.GetEnvironmentVariable("VSCMD_VER"); + Version version; + Version.TryParse(versionString, out version); if (version == null && versionString?.Contains('-') == true) { versionString = versionString.Substring(0, versionString.IndexOf('-')); - _ = Version.TryParse(versionString, out version); + Version.TryParse(versionString, out version); } if (version == null) { versionString = Environment.GetEnvironmentVariable("VisualStudioVersion"); - _ = Version.TryParse(versionString, out version); + Version.TryParse(versionString, out version); } - if (version != null) + if(version != null) { return new VisualStudioInstance("DEVCONSOLE", path, version, DiscoveryType.DeveloperConsole); } diff --git a/src/MSBuildLocator/NativeMethods.cs b/src/MSBuildLocator/NativeMethods.cs index b42277ce..3a2b2371 100644 --- a/src/MSBuildLocator/NativeMethods.cs +++ b/src/MSBuildLocator/NativeMethods.cs @@ -8,6 +8,8 @@ namespace Microsoft.Build.Locator { internal class NativeMethods { + internal const string HostFxrName = "hostfxr"; + internal enum hostfxr_resolve_sdk2_flags_t { disallow_prerelease = 0x1, @@ -30,14 +32,14 @@ internal delegate void hostfxr_get_available_sdks_result_fn( [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] string[] value); - [DllImport("hostfxr", CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + [DllImport(HostFxrName, CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] internal static extern int hostfxr_resolve_sdk2( string exe_dir, string working_dir, hostfxr_resolve_sdk2_flags_t flags, hostfxr_resolve_sdk2_result_fn result); - [DllImport("hostfxr", CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] + [DllImport(HostFxrName, CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] internal static extern int hostfxr_get_available_sdks(string exe_dir, hostfxr_get_available_sdks_result_fn result); [DllImport("libc", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] diff --git a/src/MSBuildLocator/Properties/AssemblyInfo.cs b/src/MSBuildLocator/Properties/AssemblyInfo.cs index 889c4d27..074d5ee6 100644 --- a/src/MSBuildLocator/Properties/AssemblyInfo.cs +++ b/src/MSBuildLocator/Properties/AssemblyInfo.cs @@ -3,4 +3,4 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("Microsoft.Build.Locator.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100adad416086fc859fe034cc45ba1ae17cbcaa8d471c5884ef08af87f7cd4e1bbb663e1387f24bbc479d913f125643e19d6da998b0b31a2979abcd36a858756676a65f36b27d6b6a3fd330e20ed5f73da134938bbbf90276d1fcf2f887e44e0eca4d767e147d95220433e5a4e14dcb6e6cea955e360a53642985b407a63e21f0ab")] +[assembly: InternalsVisibleTo("Microsoft.Build.Locator.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100adad416086fc859fe034cc45ba1ae17cbcaa8d471c5884ef08af87f7cd4e1bbb663e1387f24bbc479d913f125643e19d6da998b0b31a2979abcd36a858756676a65f36b27d6b6a3fd330e20ed5f73da134938bbbf90276d1fcf2f887e44e0eca4d767e147d95220433e5a4e14dcb6e6cea955e360a53642985b407a63e21f0ab")] \ No newline at end of file diff --git a/src/MSBuildLocator/Utils/SemanticVersion.cs b/src/MSBuildLocator/Utils/SemanticVersion.cs index 971adc0b..13ef2501 100644 --- a/src/MSBuildLocator/Utils/SemanticVersion.cs +++ b/src/MSBuildLocator/Utils/SemanticVersion.cs @@ -5,12 +5,13 @@ using System.Collections.Generic; using System.Linq; -namespace Microsoft.Build.Locator.Utils +namespace Microsoft.Build.Locator { internal class SemanticVersion : IComparable { private readonly IEnumerable _releaseLabels; - private readonly Version _version; + private Version _version; + private string _originalValue; public SemanticVersion(Version version, IEnumerable releaseLabels, string originalValue) { @@ -21,7 +22,7 @@ public SemanticVersion(Version version, IEnumerable releaseLabels, strin _releaseLabels = releaseLabels.ToArray(); } - OriginalValue = originalValue; + _originalValue = originalValue; } /// @@ -44,12 +45,12 @@ public SemanticVersion(Version version, IEnumerable releaseLabels, strin /// public IEnumerable ReleaseLabels => _releaseLabels ?? Enumerable.Empty(); - public string OriginalValue { get; } + public string OriginalValue => _originalValue; /// /// The full pre-release label for the version. /// - public string Release => _releaseLabels != null ? string.Join(".", _releaseLabels) : string.Empty; + public string Release => _releaseLabels != null ? String.Join(".", _releaseLabels) : String.Empty; /// /// True if pre-release labels exist for the version. @@ -60,8 +61,8 @@ public bool IsPrerelease { if (ReleaseLabels != null) { - IEnumerator enumerator = ReleaseLabels.GetEnumerator(); - return enumerator.MoveNext() && !string.IsNullOrEmpty(enumerator.Current); + var enumerator = ReleaseLabels.GetEnumerator(); + return (enumerator.MoveNext() && !String.IsNullOrEmpty(enumerator.Current)); } return false; @@ -71,6 +72,9 @@ public bool IsPrerelease /// /// Compare versions. /// - public int CompareTo(SemanticVersion other) => VersionComparer.Compare(this, other); + public int CompareTo(SemanticVersion other) + { + return VersionComparer.Compare(this, other); + } } } diff --git a/src/MSBuildLocator/Utils/SemanticVersionParser.cs b/src/MSBuildLocator/Utils/SemanticVersionParser.cs index 808117a0..9bea0025 100644 --- a/src/MSBuildLocator/Utils/SemanticVersionParser.cs +++ b/src/MSBuildLocator/Utils/SemanticVersionParser.cs @@ -5,7 +5,6 @@ using System; using System.Linq; -using Microsoft.Build.Locator.Utils; namespace Microsoft.Build.Locator { @@ -25,7 +24,7 @@ public static bool TryParse(string value, out SemanticVersion version) if (value != null) { - (string versionString, string[] releaseLabels) = ParseSections(value); + var (versionString, releaseLabels) = ParseSections(value); if (Version.TryParse(versionString, out Version systemVersion)) { @@ -86,7 +85,7 @@ private static bool IsValidPart(char[] chars, bool allowLeadingZeros) // 0 is fine, but 00 is not. // 0A counts as an alpha numeric string where zeros are not counted - if (!allowLeadingZeros && chars.Length > 1 && chars[0] == '0' && chars.All(c => char.IsDigit(c))) + if (!allowLeadingZeros && chars.Length > 1 && chars[0] == '0' && chars.All(c => Char.IsDigit(c))) { // no leading zeros in labels allowed result = false; @@ -167,4 +166,4 @@ private static Version NormalizeVersionValue(Version version) } } } -#endif +#endif \ No newline at end of file diff --git a/src/MSBuildLocator/Utils/VersionComparer.cs b/src/MSBuildLocator/Utils/VersionComparer.cs index 1d3707fa..2197cb25 100644 --- a/src/MSBuildLocator/Utils/VersionComparer.cs +++ b/src/MSBuildLocator/Utils/VersionComparer.cs @@ -4,9 +4,9 @@ using System; using System.Collections.Generic; -namespace Microsoft.Build.Locator.Utils +namespace Microsoft.Build.Locator { - internal sealed class VersionComparer + internal static class VersionComparer { /// /// Determines if both versions are equal. @@ -18,17 +18,17 @@ internal sealed class VersionComparer /// public static int Compare(SemanticVersion x, SemanticVersion y) { - if (ReferenceEquals(x, y)) + if (Object.ReferenceEquals(x, y)) { return 0; } - if (y is null) + if (Object.ReferenceEquals(y, null)) { return 1; } - if (x is null) + if (Object.ReferenceEquals(x, null)) { return -1; } @@ -40,7 +40,7 @@ public static int Compare(SemanticVersion x, SemanticVersion y) if (result != 0) { return result; - } + } result = x.Minor.CompareTo(y.Minor); if (result != 0) @@ -96,20 +96,20 @@ private static int CompareReleaseLabels(IEnumerable version1, IEnumerabl if (!aExists && bExists) { return -1; - } + } if (aExists && !bExists) { return 1; } - + result = CompareRelease(a.Current, b.Current); if (result != 0) { return result; } - + aExists = a.MoveNext(); bExists = b.MoveNext(); } @@ -126,8 +126,8 @@ private static int CompareRelease(string version1, string version2) int result; // check if the identifiers are numeric - bool v1IsNumeric = int.TryParse(version1, out int version1Num); - bool v2IsNumeric = int.TryParse(version2, out int version2Num); + bool v1IsNumeric = Int32.TryParse(version1, out int version1Num); + bool v2IsNumeric = Int32.TryParse(version2, out int version2Num); // if both are numeric compare them as numbers if (v1IsNumeric && v2IsNumeric) diff --git a/src/MSBuildLocator/VisualStudioInstance.cs b/src/MSBuildLocator/VisualStudioInstance.cs index 34c58cd1..e8185dec 100644 --- a/src/MSBuildLocator/VisualStudioInstance.cs +++ b/src/MSBuildLocator/VisualStudioInstance.cs @@ -60,4 +60,4 @@ internal VisualStudioInstance(string name, string path, Version version, Discove /// public DiscoveryType DiscoveryType { get; } } -} +} \ No newline at end of file diff --git a/src/MSBuildLocator/VisualStudioLocationHelper.cs b/src/MSBuildLocator/VisualStudioLocationHelper.cs index 7197b0f8..87d1c22d 100644 --- a/src/MSBuildLocator/VisualStudioLocationHelper.cs +++ b/src/MSBuildLocator/VisualStudioLocationHelper.cs @@ -1,6 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. - // Taken from https://github.com/Microsoft/msbuild/blob/6851538897f5d7b08024a6d8435bc44be5869e53/src/Shared/VisualStudioLocationHelper.cs #if FEATURE_VISUALSTUDIOSETUP @@ -19,7 +18,7 @@ namespace Microsoft.Build.Locator /// internal class VisualStudioLocationHelper { - private const int REGDB_E_CLASSNOTREG = unchecked((int)0x80040154); + private const int REGDB_E_CLASSNOTREG = unchecked((int) 0x80040154); /// /// Query the Visual Studio setup API to get instances of Visual Studio installed @@ -33,8 +32,8 @@ internal static IList GetInstances() try { // This code is not obvious. See the sample (link above) for reference. - var query = (ISetupConfiguration2)GetQuery(); - IEnumSetupInstances e = query.EnumAllInstances(); + var query = (ISetupConfiguration2) GetQuery(); + var e = query.EnumAllInstances(); int fetched; var instances = new ISetupInstance[1]; @@ -42,18 +41,13 @@ internal static IList GetInstances() { // Call e.Next to query for the next instance (single item or nothing returned). e.Next(1, instances, out fetched); - if (fetched <= 0) - { - continue; - } + if (fetched <= 0) continue; - var instance = (ISetupInstance2)instances[0]; + var instance = (ISetupInstance2) instances[0]; InstanceState state = instance.GetState(); if (!Version.TryParse(instance.GetInstallationVersion(), out Version version)) - { continue; - } // If the install was complete and a valid version, consider it. if (state == InstanceState.Complete || @@ -102,15 +96,19 @@ private static ISetupConfiguration GetQuery() catch (COMException ex) when (ex.ErrorCode == REGDB_E_CLASSNOTREG) { // Try to get the class object using app-local call. - int result = GetSetupConfiguration(out ISetupConfiguration query, IntPtr.Zero); + ISetupConfiguration query; + var result = GetSetupConfiguration(out query, IntPtr.Zero); + + if (result < 0) + throw new COMException($"Failed to get {nameof(query)}", result); - return result < 0 ? throw new COMException($"Failed to get {nameof(query)}", result) : query; + return query; } } [DllImport("Microsoft.VisualStudio.Setup.Configuration.Native.dll", ExactSpelling = true, PreserveSig = true)] private static extern int GetSetupConfiguration( - [MarshalAs(UnmanagedType.Interface)][Out] out ISetupConfiguration configuration, + [MarshalAs(UnmanagedType.Interface)] [Out] out ISetupConfiguration configuration, IntPtr reserved); } } From 4914f9b7c94586b5fe4ef827014b2269bc2d952e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Sep 2023 11:28:40 +0200 Subject: [PATCH 7/8] Bump xunit.runner.visualstudio from 2.5.0 to 2.5.1 (#245) Bumps [xunit.runner.visualstudio](https://github.com/xunit/xunit) from 2.5.0 to 2.5.1. - [Commits](https://github.com/xunit/xunit/compare/2.5.0...2.5.1) --- updated-dependencies: - dependency-name: xunit.runner.visualstudio dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/MSBuildLocator.Tests/Microsoft.Build.Locator.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MSBuildLocator.Tests/Microsoft.Build.Locator.Tests.csproj b/src/MSBuildLocator.Tests/Microsoft.Build.Locator.Tests.csproj index 4c8d0abf..ddc51a6f 100644 --- a/src/MSBuildLocator.Tests/Microsoft.Build.Locator.Tests.csproj +++ b/src/MSBuildLocator.Tests/Microsoft.Build.Locator.Tests.csproj @@ -11,7 +11,7 @@ - + From 65af3233f4517425fc759726b99ba963a38ebc4b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Sep 2023 11:36:49 +0200 Subject: [PATCH 8/8] Bump xunit from 2.5.0 to 2.5.1 (#246) Bumps [xunit](https://github.com/xunit/xunit) from 2.5.0 to 2.5.1. - [Commits](https://github.com/xunit/xunit/compare/2.5.0...2.5.1) --- updated-dependencies: - dependency-name: xunit dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/MSBuildLocator.Tests/Microsoft.Build.Locator.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MSBuildLocator.Tests/Microsoft.Build.Locator.Tests.csproj b/src/MSBuildLocator.Tests/Microsoft.Build.Locator.Tests.csproj index ddc51a6f..8edfc2cd 100644 --- a/src/MSBuildLocator.Tests/Microsoft.Build.Locator.Tests.csproj +++ b/src/MSBuildLocator.Tests/Microsoft.Build.Locator.Tests.csproj @@ -10,7 +10,7 @@ - +