diff --git a/src/Build.UnitTests/BackEnd/CacheAggregator_Tests.cs b/src/Build.UnitTests/BackEnd/CacheAggregator_Tests.cs index bbe0f749387..488f5f8bfd9 100644 --- a/src/Build.UnitTests/BackEnd/CacheAggregator_Tests.cs +++ b/src/Build.UnitTests/BackEnd/CacheAggregator_Tests.cs @@ -33,10 +33,10 @@ public void NoCachesProducesEmptyCaches() var aggregation = aggregator.Aggregate(); aggregation.ConfigCache.ShouldNotBeNull(); - aggregation.ConfigCache.GetEnumerator().ToEnumerable().ShouldBeEmpty(); + aggregation.ConfigCache.ShouldBeEmpty(); aggregation.ResultsCache.ShouldNotBeNull(); - aggregation.ResultsCache.GetEnumerator().ToEnumerable().ShouldBeEmpty(); + aggregation.ResultsCache.ShouldBeEmpty(); aggregation.LastConfigurationId.ShouldBe(0); } @@ -246,9 +246,9 @@ private void AssertAggregation((ConfigCache configCache, ResultsCache resultsCac var currentConfigurationIndex = 0; var currentBuildResultIndex = 0; - var aggregatedConfigs = aggregation.ConfigCache.GetEnumerator().ToArray(); + var aggregatedConfigs = aggregation.ConfigCache.ToArray(); - var aggregatedResults = aggregation.ResultsCache.GetEnumerator().ToArray(); + var aggregatedResults = aggregation.ResultsCache.ToArray(); foreach (var (configCache, resultsCache) in inputCaches) { diff --git a/src/Build.UnitTests/BackEnd/ConfigCache_Tests.cs b/src/Build.UnitTests/BackEnd/ConfigCache_Tests.cs index 3ca18bc9832..bf40853c4e8 100644 --- a/src/Build.UnitTests/BackEnd/ConfigCache_Tests.cs +++ b/src/Build.UnitTests/BackEnd/ConfigCache_Tests.cs @@ -101,8 +101,8 @@ public void ConfigCacheShouldBeTranslatable(object obj) TranslationHelpers.GetReadTranslator().Translate(ref copy); // test _configurations - var initialConfigurations = initial.GetEnumerator().ToArray(); - var copiedConfigurations = copy.GetEnumerator().ToArray(); + var initialConfigurations = initial.ToArray(); + var copiedConfigurations = copy.ToArray(); Assert.Equal(copiedConfigurations, initialConfigurations, EqualityComparer.Default); diff --git a/src/Build.UnitTests/BackEnd/ResultsCache_Tests.cs b/src/Build.UnitTests/BackEnd/ResultsCache_Tests.cs index 7fc43eccc59..eceb5123067 100644 --- a/src/Build.UnitTests/BackEnd/ResultsCache_Tests.cs +++ b/src/Build.UnitTests/BackEnd/ResultsCache_Tests.cs @@ -80,7 +80,7 @@ public void CacheCanBeEnumerated() result2.AddResultsForTarget("result2target1", BuildResultUtilities.GetEmptyFailingTargetResult()); cache.AddResult(result2); - var results = cache.GetEnumerator().ToArray(); + var results = cache.ToArray(); results.Length.ShouldBe(2); diff --git a/src/Build.UnitTests/FileUtilitiesRegex_Tests.cs b/src/Build.UnitTests/FileUtilitiesRegex_Tests.cs index 72a46b6d23b..80781a43671 100644 --- a/src/Build.UnitTests/FileUtilitiesRegex_Tests.cs +++ b/src/Build.UnitTests/FileUtilitiesRegex_Tests.cs @@ -532,7 +532,6 @@ public void PatternEmptyString_LegacyRegex() { UncPattern.IsMatch(string.Empty).ShouldBeFalse(); StartsWithUncPattern.IsMatch(string.Empty).ShouldBeFalse(); - StartsWithUncPattern.Match(string.Empty).Success.ShouldBeFalse(); } [Fact] diff --git a/src/Build.UnitTests/Globbing/MSBuildGlob_Tests.cs b/src/Build.UnitTests/Globbing/MSBuildGlob_Tests.cs index cc551762e11..8156ed4fb52 100644 --- a/src/Build.UnitTests/Globbing/MSBuildGlob_Tests.cs +++ b/src/Build.UnitTests/Globbing/MSBuildGlob_Tests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Buffers; using System.Collections.Generic; using System.IO; using System.Linq; @@ -70,9 +71,12 @@ public void GlobFromRelativeGlobRootNormalizesRootAgainstCurrentDirectory() [Fact] public void GlobFromRootWithInvalidPathThrows() { - foreach (var invalidPathChar in FileUtilities.InvalidPathChars) + for (int i = 0; i < 128; i++) { - Assert.Throws(() => MSBuildGlob.Parse(invalidPathChar.ToString(), "*")); + if (FileUtilities.InvalidPathChars.Contains((char)i)) + { + Assert.Throws(() => MSBuildGlob.Parse(((char)i).ToString(), "*")); + } } } @@ -182,12 +186,15 @@ public void GlobMatchShouldReturnFalseIfArgumentContainsInvalidPathOrFileCharact { var glob = MSBuildGlob.Parse("*"); - foreach (var invalidPathChar in FileUtilities.InvalidPathChars) + for (int i = 0; i < 128; i++) { - Assert.False(glob.IsMatch(invalidPathChar.ToString())); + if (FileUtilities.InvalidPathChars.Contains((char)i)) + { + Assert.False(glob.IsMatch(((char)i).ToString())); + } } - foreach (var invalidFileChar in FileUtilities.InvalidFileNameChars) + foreach (var invalidFileChar in FileUtilities.InvalidFileNameCharsArray) { if (invalidFileChar == '\\' || invalidFileChar == '/') { diff --git a/src/Build.UnitTests/Graph/IsolateProjects_Tests.cs b/src/Build.UnitTests/Graph/IsolateProjects_Tests.cs index 063df0be739..d799c4267b1 100644 --- a/src/Build.UnitTests/Graph/IsolateProjects_Tests.cs +++ b/src/Build.UnitTests/Graph/IsolateProjects_Tests.cs @@ -338,8 +338,8 @@ public void UndeclaredReferenceBuildResultNotPresentInOutputCache() var deserializedOutputCacheRoot = CacheSerialization.DeserializeCaches(outputCaches[topoSortedProjectGraphNodes[1]]); deserializedOutputCacheDeclaredReference.exception.ShouldBeNull(); deserializedOutputCacheRoot.exception.ShouldBeNull(); - BuildResult[] declaredReferenceBuildResults = deserializedOutputCacheDeclaredReference.ResultsCache.GetEnumerator().ToArray(); - BuildResult[] rootBuildResults = deserializedOutputCacheRoot.ResultsCache.GetEnumerator().ToArray(); + BuildResult[] declaredReferenceBuildResults = deserializedOutputCacheDeclaredReference.ResultsCache.ToArray(); + BuildResult[] rootBuildResults = deserializedOutputCacheRoot.ResultsCache.ToArray(); // Both the root and declared reference projects should only have one build result. declaredReferenceBuildResults.Length.ShouldBe(1); diff --git a/src/Build.UnitTests/Graph/ResultCacheBasedBuilds_Tests.cs b/src/Build.UnitTests/Graph/ResultCacheBasedBuilds_Tests.cs index b1a0b664c80..8ff5f52352f 100644 --- a/src/Build.UnitTests/Graph/ResultCacheBasedBuilds_Tests.cs +++ b/src/Build.UnitTests/Graph/ResultCacheBasedBuilds_Tests.cs @@ -356,13 +356,13 @@ public void OutputCacheShouldNotContainInformationFromInputCaches() deserializationInfo.exception.ShouldBeNull(); - var buildResults = deserializationInfo.ResultsCache.GetEnumerator().ToArray(); + var buildResults = deserializationInfo.ResultsCache.ToArray(); buildResults.ShouldHaveSingleItem(); var rootNodeBuildResult = buildResults.First(); rootNodeBuildResult.ResultsByTarget["Build"].Items.Select(i => i.ItemSpec).ToArray().ShouldBe(expectedOutput[rootNode]); - var configEntries = deserializationInfo.ConfigCache.GetEnumerator().ToArray(); + var configEntries = deserializationInfo.ConfigCache.ToArray(); configEntries.ShouldHaveSingleItem(); configEntries.First().ConfigurationId.ShouldBe(rootNodeBuildResult.ConfigurationId); diff --git a/src/Build/BackEnd/BuildManager/BuildManager.cs b/src/Build/BackEnd/BuildManager/BuildManager.cs index 4dbf79918ac..78f96a187df 100644 --- a/src/Build/BackEnd/BuildManager/BuildManager.cs +++ b/src/Build/BackEnd/BuildManager/BuildManager.cs @@ -2801,12 +2801,11 @@ private NodeConfiguration GetNodeConfiguration() { // Get the remote loggers ILoggingService loggingService = ((IBuildComponentHost)this).GetComponent(BuildComponentType.LoggingService); - var remoteLoggers = new List(loggingService.LoggerDescriptions); _nodeConfiguration = new NodeConfiguration( -1, /* must be assigned by the NodeManager */ _buildParameters, - remoteLoggers.ToArray() + loggingService.LoggerDescriptions.ToArray() #if FEATURE_APPDOMAIN , AppDomain.CurrentDomain.SetupInformation #endif diff --git a/src/Build/BackEnd/BuildManager/CacheAggregator.cs b/src/Build/BackEnd/BuildManager/CacheAggregator.cs index aca3b8ef9c7..ddbe4524e06 100644 --- a/src/Build/BackEnd/BuildManager/CacheAggregator.cs +++ b/src/Build/BackEnd/BuildManager/CacheAggregator.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using Microsoft.Build.BackEnd; using Microsoft.Build.Framework; using Microsoft.Build.Internal; @@ -55,8 +56,8 @@ public CacheAggregation Aggregate() private void InsertCaches(IConfigCache configCache, IResultsCache resultsCache) { - var configs = configCache.GetEnumerator().ToArray(); - var results = resultsCache.GetEnumerator().ToArray(); + var configs = configCache.ToArray(); + var results = resultsCache.ToArray(); ErrorUtilities.VerifyThrow(configs.Length == results.Length, "Assuming 1-to-1 mapping between configs and results. Otherwise it means the caches are either not minimal or incomplete"); diff --git a/src/Build/BackEnd/Client/MSBuildClientPacketPump.cs b/src/Build/BackEnd/Client/MSBuildClientPacketPump.cs index 65a7b72a4dd..a7234030b5c 100644 --- a/src/Build/BackEnd/Client/MSBuildClientPacketPump.cs +++ b/src/Build/BackEnd/Client/MSBuildClientPacketPump.cs @@ -195,7 +195,7 @@ private void RunReadLoop(Stream localStream, ManualResetEvent localPacketPumpShu #if FEATURE_APM IAsyncResult result = localStream.BeginRead(headerByte, 0, headerByte.Length, null, null); #else - Task readTask = CommunicationsUtilities.ReadAsync(localStream, headerByte, headerByte.Length); + Task readTask = CommunicationsUtilities.ReadAsync(localStream, headerByte, headerByte.Length).AsTask(); #endif bool continueReading = true; @@ -294,7 +294,7 @@ private void RunReadLoop(Stream localStream, ManualResetEvent localPacketPumpShu #if FEATURE_APM result = localStream.BeginRead(headerByte, 0, headerByte.Length, null, null); #else - readTask = CommunicationsUtilities.ReadAsync(localStream, headerByte, headerByte.Length); + readTask = CommunicationsUtilities.ReadAsync(localStream, headerByte, headerByte.Length).AsTask(); #endif } } diff --git a/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs b/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs index e3b0a6d069e..a87175b7936 100644 --- a/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs +++ b/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs @@ -1433,7 +1433,7 @@ private void TraceEngine(string format, params object[] stuff) using (StreamWriter file = FileUtilities.OpenWrite(string.Format(CultureInfo.CurrentCulture, Path.Combine(_debugDumpPath, @"EngineTrace_{0}.txt"), EnvironmentUtilities.CurrentProcessId), append: true)) { string message = String.Format(CultureInfo.CurrentCulture, format, stuff); - file.WriteLine("{0}({1})-{2}: {3}", Thread.CurrentThread.Name, Thread.CurrentThread.ManagedThreadId, DateTime.UtcNow.Ticks, message); + file.WriteLine("{0}({1})-{2}: {3}", Thread.CurrentThread.Name, Environment.CurrentManagedThreadId, DateTime.UtcNow.Ticks, message); file.Flush(); } } diff --git a/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEntry.cs b/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEntry.cs index 28466f039ca..2450c2debfd 100644 --- a/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEntry.cs +++ b/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEntry.cs @@ -511,12 +511,13 @@ private void WaitForResult(BuildRequest newRequest, bool addToIssueList) ErrorUtilities.VerifyThrow(addToIssueList, "Requests with unresolved configurations should always be added to the issue list."); _unresolvedConfigurations ??= new Dictionary>(); - if (!_unresolvedConfigurations.ContainsKey(newRequest.ConfigurationId)) + if (!_unresolvedConfigurations.TryGetValue(newRequest.ConfigurationId, out List value)) { - _unresolvedConfigurations.Add(newRequest.ConfigurationId, new List()); + value = new List(); + _unresolvedConfigurations.Add(newRequest.ConfigurationId, value); } - _unresolvedConfigurations[newRequest.ConfigurationId].Add(newRequest); + value.Add(newRequest); } if (addToIssueList) diff --git a/src/Build/BackEnd/Components/Communications/NodeEndpointInProc.cs b/src/Build/BackEnd/Components/Communications/NodeEndpointInProc.cs index 6fdad8b8128..2c90dda389c 100644 --- a/src/Build/BackEnd/Components/Communications/NodeEndpointInProc.cs +++ b/src/Build/BackEnd/Components/Communications/NodeEndpointInProc.cs @@ -29,7 +29,7 @@ internal class NodeEndpointInProc : INodeEndpoint /// /// An object for the two inproc endpoints to synchronize on. /// - private static Object s_locker = new Object(); + private static readonly Object s_locker = new Object(); /// /// The current communication status of the node. diff --git a/src/Build/BackEnd/Components/Communications/NodeProviderInProc.cs b/src/Build/BackEnd/Components/Communications/NodeProviderInProc.cs index 15c815fb9cf..61ff495b01f 100644 --- a/src/Build/BackEnd/Components/Communications/NodeProviderInProc.cs +++ b/src/Build/BackEnd/Components/Communications/NodeProviderInProc.cs @@ -382,7 +382,7 @@ private bool InstantiateNode(INodePacketFactory factory) InProcNodeThreadProc(); }); #endif - _inProcNodeThread.Name = String.Format(CultureInfo.CurrentCulture, "In-proc Node ({0})", _componentHost.Name); + _inProcNodeThread.Name = $"In-proc Node ({_componentHost.Name})"; _inProcNodeThread.IsBackground = true; #if FEATURE_THREAD_CULTURE _inProcNodeThread.CurrentCulture = _componentHost.BuildParameters.Culture; diff --git a/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs b/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs index c506179bcd6..b1e380b5fa2 100644 --- a/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs +++ b/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs @@ -416,7 +416,11 @@ void CreateNodeContext(int nodeId, Process nodeToReuse, Stream nodeStream) /// private string GetProcessesToIgnoreKey(Handshake hostHandshake, int nodeProcessId) { - return hostHandshake.ToString() + "|" + nodeProcessId.ToString(CultureInfo.InvariantCulture); +#if NET + return string.Create(CultureInfo.InvariantCulture, $"{hostHandshake}|{nodeProcessId}"); +#else + return $"{hostHandshake}|{nodeProcessId.ToString(CultureInfo.InvariantCulture)}"; +#endif } #if !FEATURE_PIPEOPTIONS_CURRENTUSERONLY @@ -833,8 +837,17 @@ public async Task WaitForExitAsync(ILoggingService loggingService) { // Wait up to 100ms until all remaining packets are sent. // We don't need to wait long, just long enough for the Task to start running on the ThreadPool. - await Task.WhenAny(_packetWriteDrainTask, Task.Delay(100)); +#if NET + await _packetWriteDrainTask.WaitAsync(TimeSpan.FromMilliseconds(100)).ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing); +#else + using (var cts = new CancellationTokenSource(100)) + { + await Task.WhenAny(_packetWriteDrainTask, Task.Delay(100, cts.Token)); + cts.Cancel(); + } +#endif } + if (_exitPacketState == ExitPacketState.ExitPacketSent) { CommunicationsUtilities.Trace("Waiting for node with pid = {0} to exit", _process.Id); diff --git a/src/Build/BackEnd/Components/Communications/TranslatorExtensions.cs b/src/Build/BackEnd/Components/Communications/TranslatorExtensions.cs index 912c37ee0ca..ab2b1bb2da0 100644 --- a/src/Build/BackEnd/Components/Communications/TranslatorExtensions.cs +++ b/src/Build/BackEnd/Components/Communications/TranslatorExtensions.cs @@ -20,7 +20,7 @@ namespace Microsoft.Build.BackEnd /// internal static class TranslatorExtensions { - private static Lazy> parameterlessConstructorCache = new Lazy>(() => new ConcurrentDictionary()); + private static readonly Lazy> parameterlessConstructorCache = new Lazy>(() => new ConcurrentDictionary()); /// /// Translates a PropertyDictionary of ProjectPropertyInstances. diff --git a/src/Build/BackEnd/Components/FileAccesses/FileAccessManager.cs b/src/Build/BackEnd/Components/FileAccesses/FileAccessManager.cs index de28c100d1d..5936bf90ac5 100644 --- a/src/Build/BackEnd/Components/FileAccesses/FileAccessManager.cs +++ b/src/Build/BackEnd/Components/FileAccesses/FileAccessManager.cs @@ -22,7 +22,7 @@ private record Handlers(Action FileAccessHander, A // is used to mark when the file accesses should be considered complete. Only after both this special file access is seen // and the build result is reported can plugins be notified about project completion. // NOTE! This is currently Windows-specific and will need to change once this feature is opened up to more scenarios. - private static readonly string FileAccessCompletionPrefix = BuildParameters.StartupDirectory[0] + @":\{MSBuildFileAccessCompletion}\"; + private static readonly string FileAccessCompletionPrefix = $@"{BuildParameters.StartupDirectory[0]}:\{{MSBuildFileAccessCompletion}}\"; private IScheduler? _scheduler; private IConfigCache? _configCache; diff --git a/src/Build/BackEnd/Components/Logging/LoggingService.cs b/src/Build/BackEnd/Components/Logging/LoggingService.cs index c08622d49eb..1e4121594e2 100644 --- a/src/Build/BackEnd/Components/Logging/LoggingService.cs +++ b/src/Build/BackEnd/Components/Logging/LoggingService.cs @@ -94,7 +94,7 @@ internal partial class LoggingService : ILoggingService, INodePacketHandler /// We use a BindingFlags.Public flag here because the getter is public, so although the setter is internal, /// it is only discoverable with Reflection using the Public flag (go figure!) /// - private static Lazy s_projectStartedEventArgsGlobalProperties = new Lazy(() => typeof(ProjectStartedEventArgs).GetProperty("GlobalProperties", BindingFlags.Public | BindingFlags.Instance), LazyThreadSafetyMode.PublicationOnly); + private static readonly Lazy s_projectStartedEventArgsGlobalProperties = new Lazy(() => typeof(ProjectStartedEventArgs).GetProperty("GlobalProperties", BindingFlags.Public | BindingFlags.Instance), LazyThreadSafetyMode.PublicationOnly); /// /// A cached reflection accessor for an internal member. @@ -103,7 +103,7 @@ internal partial class LoggingService : ILoggingService, INodePacketHandler /// We use a BindingFlags.Public flag here because the getter is public, so although the setter is internal, /// it is only discoverable with Reflection using the Public flag (go figure!) /// - private static Lazy s_projectStartedEventArgsToolsVersion = new Lazy(() => typeof(ProjectStartedEventArgs).GetProperty("ToolsVersion", BindingFlags.Public | BindingFlags.Instance), LazyThreadSafetyMode.PublicationOnly); + private static readonly Lazy s_projectStartedEventArgsToolsVersion = new Lazy(() => typeof(ProjectStartedEventArgs).GetProperty("ToolsVersion", BindingFlags.Public | BindingFlags.Instance), LazyThreadSafetyMode.PublicationOnly); #region Data diff --git a/src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs b/src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs index bd1cb0fd8d7..8906f10ba5a 100644 --- a/src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs +++ b/src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs @@ -32,7 +32,7 @@ internal sealed class ProjectCacheService : IAsyncDisposable { private static readonly ParallelOptions s_parallelOptions = new() { MaxDegreeOfParallelism = Environment.ProcessorCount }; - private static HashSet s_projectSpecificPropertyNames = new(StringComparer.OrdinalIgnoreCase) { "TargetFramework", "Configuration", "Platform", "TargetPlatform", "OutputType" }; + private static readonly HashSet s_projectSpecificPropertyNames = new(StringComparer.OrdinalIgnoreCase) { "TargetFramework", "Configuration", "Platform", "TargetPlatform", "OutputType" }; private readonly BuildManager _buildManager; private readonly IBuildComponentHost _componentHost; @@ -115,8 +115,7 @@ public void InitializePluginsForGraph( foreach (ProjectCacheDescriptor projectCacheDescriptor in GetProjectCacheDescriptors(node.ProjectInstance)) { // Intentionally fire-and-forget to asynchronously initialize the plugin. Any exceptions will bubble up later when querying. - _ = GetProjectCachePluginAsync(projectCacheDescriptor, projectGraph, buildRequestConfiguration: null, requestedTargets, cancellationToken) - .ContinueWith(t => { }, TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnFaulted); + _ = GetProjectCachePluginAsync(projectCacheDescriptor, projectGraph, buildRequestConfiguration: null, requestedTargets, cancellationToken); } }); }, @@ -149,8 +148,7 @@ public void InitializePluginsForVsScenario( projectCacheDescriptor => { // Intentionally fire-and-forget to asynchronously initialize the plugin. Any exceptions will bubble up later when querying. - _ = GetProjectCachePluginAsync(projectCacheDescriptor, projectGraph: null, buildRequestConfiguration, requestedTargets, cancellationToken) - .ContinueWith(t => { }, TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnFaulted); + _ = GetProjectCachePluginAsync(projectCacheDescriptor, projectGraph: null, buildRequestConfiguration, requestedTargets, cancellationToken); }); }, cancellationToken); @@ -449,7 +447,7 @@ public void PostCacheRequest(CacheRequest cacheRequest, CancellationToken cancel }, cancellationToken); - async Task<(CacheResult Result, int ProjectContextId)> ProcessCacheRequestAsync() + async ValueTask<(CacheResult Result, int ProjectContextId)> ProcessCacheRequestAsync() { EvaluateProjectIfNecessary(cacheRequest.Submission, cacheRequest.Configuration); @@ -499,7 +497,7 @@ void EvaluateProjectIfNecessary(BuildSubmission submission, BuildRequestConfigur } } - private async Task GetCacheResultAsync(BuildRequestData buildRequest, BuildRequestConfiguration buildRequestConfiguration, BuildEventContext buildEventContext, CancellationToken cancellationToken) + private async ValueTask GetCacheResultAsync(BuildRequestData buildRequest, BuildRequestConfiguration buildRequestConfiguration, BuildEventContext buildEventContext, CancellationToken cancellationToken) { ErrorUtilities.VerifyThrowInternalNull(buildRequest.ProjectInstance, nameof(buildRequest.ProjectInstance)); diff --git a/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/ItemGroupIntrinsicTask.cs b/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/ItemGroupIntrinsicTask.cs index 423679a1f6e..c33c0ab6563 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/ItemGroupIntrinsicTask.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/ItemGroupIntrinsicTask.cs @@ -186,7 +186,7 @@ private void ExecuteAdd(ProjectItemGroupTaskItemInstance child, ItemBucket bucke if (// If multiple buckets were expanded - we do not want to repeat same error for same metadatum on a same line bucket.BucketSequenceNumber == 0 && // Referring to unqualified metadata of other item (transform) is fine. - child.Include.IndexOf("@(", StringComparison.Ordinal) == -1) + !child.Include.Contains("@(")) { expanderOptions |= ExpanderOptions.LogOnItemMetadataSelfReference; } diff --git a/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/MSBuild.cs b/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/MSBuild.cs index 73f84dd94a0..9c2271ff635 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/MSBuild.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/MSBuild.cs @@ -259,7 +259,7 @@ public async Task ExecuteInternal() undefinePropertiesArray = RemoveProperties.Split(MSBuildConstants.SemicolonChar, StringSplitOptions.RemoveEmptyEntries); foreach (string property in undefinePropertiesArray) { - Log.LogMessageFromText(String.Format(CultureInfo.InvariantCulture, " {0}", property), MessageImportance.Low); + Log.LogMessageFromText($" {property}", MessageImportance.Low); } } @@ -296,10 +296,7 @@ public async Task ExecuteInternal() if (BuildInParallel) { skipProjects = new bool[Projects.Length]; - for (int i = 0; i < skipProjects.Length; i++) - { - skipProjects[i] = true; - } + skipProjects.AsSpan().Fill(true); } else { @@ -594,7 +591,7 @@ internal static async Task ExecuteTargets( foreach (string property in propertiesToUndefine) { undefinePropertiesPerProject[i].Add(property); - log.LogMessageFromText(String.Format(CultureInfo.InvariantCulture, " {0}", property), MessageImportance.Low); + log.LogMessageFromText($" {property}", MessageImportance.Low); } } } diff --git a/src/Build/BackEnd/Components/RequestBuilder/Lookup.cs b/src/Build/BackEnd/Components/RequestBuilder/Lookup.cs index 6a882eb87f0..c2b180dd2b9 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/Lookup.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/Lookup.cs @@ -1373,7 +1373,7 @@ internal Scope(Lookup lookup, string description, IItemDictionary resultsList = new List(results.Values); - resultsList.Sort(delegate (BuildResult left, BuildResult right) - { - if (left.NodeRequestId < right.NodeRequestId) - { - return -1; - } - else if (left.NodeRequestId == right.NodeRequestId) - { - return 0; - } - - return 1; - }); - - return resultsList.ToArray(); + BuildResult[] resultsArray = results.Values.ToArray(); + Array.Sort(resultsArray, (left, right) => left.NodeRequestId.CompareTo(right.NodeRequestId)); + return resultsArray; } /// diff --git a/src/Build/BackEnd/Components/RequestBuilder/TargetBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/TargetBuilder.cs index aab4fb6d344..65b6903876a 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TargetBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TargetBuilder.cs @@ -412,7 +412,7 @@ private async Task ProcessTargetStack(ITaskBuilder taskBuilder) ( !_cancellationToken.IsCancellationRequested && !stopProcessingStack && - _targetsToBuild.Any()) + !_targetsToBuild.IsEmpty) { TargetEntry currentTargetEntry = _targetsToBuild.Peek(); switch (currentTargetEntry.State) @@ -621,7 +621,7 @@ private void PopDependencyTargetsOnTargetFailure(TargetEntry topEntry, TargetRes // Pop down to our parent, since any other dependencies our parent had should no longer // execute. If we encounter an error target on the way down, also stop since the failure // of one error target in a set declared in OnError should not cause the others to stop running. - while ((_targetsToBuild.Any()) && (_targetsToBuild.Peek() != topEntry.ParentEntry) && !_targetsToBuild.Peek().ErrorTarget) + while ((!_targetsToBuild.IsEmpty) && (_targetsToBuild.Peek() != topEntry.ParentEntry) && !_targetsToBuild.Peek().ErrorTarget) { TargetEntry entry = _targetsToBuild.Pop(); entry.LeaveLegacyCallTargetScopes(); diff --git a/src/Build/BackEnd/Components/RequestBuilder/TargetEntry.cs b/src/Build/BackEnd/Components/RequestBuilder/TargetEntry.cs index c85b4f41f54..30179f2e7a9 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TargetEntry.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TargetEntry.cs @@ -810,7 +810,7 @@ internal void LeaveLegacyCallTargetScopes() /// /// The result of the tasks, based on the last task which ran. /// - private async Task ProcessBucket(ITaskBuilder taskBuilder, TargetLoggingContext targetLoggingContext, TaskExecutionMode mode, Lookup lookupForInference, Lookup lookupForExecution) + private async ValueTask ProcessBucket(ITaskBuilder taskBuilder, TargetLoggingContext targetLoggingContext, TaskExecutionMode mode, Lookup lookupForInference, Lookup lookupForExecution) { WorkUnitResultCode aggregatedTaskResult = WorkUnitResultCode.Success; WorkUnitActionCode finalActionCode = WorkUnitActionCode.Continue; diff --git a/src/Build/BackEnd/Components/RequestBuilder/TargetUpToDateChecker.cs b/src/Build/BackEnd/Components/RequestBuilder/TargetUpToDateChecker.cs index a75c73a91ae..58e250f6c97 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TargetUpToDateChecker.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TargetUpToDateChecker.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Buffers; using System.Collections.Generic; using System.IO; using System.Linq; @@ -838,15 +839,14 @@ private void SeparateItemVectorsFromDiscreteItems( } // Do we already have a partition for this? - if (!itemVectorCollection.ContainsKey(itemVectorType)) + if (!itemVectorCollection.TryGetValue(itemVectorType, out ItemVectorPartition itemVectorPartition)) { // Nope, create one. - itemVectorCollection[itemVectorType] = new ItemVectorPartition(MSBuildNameIgnoreCaseComparer.Default); + itemVectorPartition = new ItemVectorPartition(MSBuildNameIgnoreCaseComparer.Default); + itemVectorCollection[itemVectorType] = itemVectorPartition; } - ItemVectorPartition itemVectorPartition = itemVectorCollection[itemVectorType]; - - ErrorUtilities.VerifyThrow(!itemVectorCollection[itemVectorType].ContainsKey(item), "ItemVectorPartition already contains a vector for items with the expression '{0}'", item); + ErrorUtilities.VerifyThrow(!itemVectorPartition.ContainsKey(item), "ItemVectorPartition already contains a vector for items with the expression '{0}'", item); itemVectorPartition[item] = itemVectorContents; ErrorUtilities.VerifyThrow((itemVectorTransforms == null) || (itemVectorCollection.Equals(itemVectorTransforms)) || (itemVectorPartition.Count == 1), @@ -1129,8 +1129,8 @@ private bool IsOutOfDate(string input, string output, string inputItemName, stri { input = EscapingUtilities.UnescapeAll(FileUtilities.FixFilePath(input)); output = EscapingUtilities.UnescapeAll(FileUtilities.FixFilePath(output)); - ProjectErrorUtilities.VerifyThrowInvalidProject(input.IndexOfAny(Path.GetInvalidPathChars()) == -1, _project.ProjectFileLocation, "IllegalCharactersInFileOrDirectory", input, inputItemName); - ProjectErrorUtilities.VerifyThrowInvalidProject(output.IndexOfAny(Path.GetInvalidPathChars()) == -1, _project.ProjectFileLocation, "IllegalCharactersInFileOrDirectory", output, outputItemName); + ProjectErrorUtilities.VerifyThrowInvalidProject(input.AsSpan().IndexOfAny(MSBuildConstants.InvalidPathChars) < 0, _project.ProjectFileLocation, "IllegalCharactersInFileOrDirectory", input, inputItemName); + ProjectErrorUtilities.VerifyThrowInvalidProject(output.AsSpan().IndexOfAny(MSBuildConstants.InvalidPathChars) < 0, _project.ProjectFileLocation, "IllegalCharactersInFileOrDirectory", output, outputItemName); bool outOfDate = (CompareLastWriteTimes(input, output, out bool inputDoesNotExist, out bool outputDoesNotExist) == 1) || inputDoesNotExist; // Only if we are not logging just critical events should we be gathering full details diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs index 3a406115fce..36b4af7301f 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs @@ -291,7 +291,7 @@ private List CreateListOfParameterValues() /// Called to execute a task within a target. This method instantiates the task, sets its parameters, and executes it. /// /// true, if successful - private async Task ExecuteTask(TaskExecutionMode mode, Lookup lookup) + private async ValueTask ExecuteTask(TaskExecutionMode mode, Lookup lookup) { ErrorUtilities.VerifyThrowArgumentNull(lookup); @@ -366,7 +366,7 @@ private async Task ExecuteTask(TaskExecutionMode mode, Lookup lo /// Execute a single bucket /// /// true if execution succeeded - private async Task ExecuteBucket(TaskHost taskHost, ItemBucket bucket, TaskExecutionMode howToExecuteTask, Dictionary lookupHash) + private async ValueTask ExecuteBucket(TaskHost taskHost, ItemBucket bucket, TaskExecutionMode howToExecuteTask, Dictionary lookupHash) { // On Intrinsic tasks, we do not allow batchable params, therefore metadata is excluded. ParserOptions parserOptions = (_taskNode == null) ? ParserOptions.AllowPropertiesAndItemLists : ParserOptions.AllowAll; @@ -738,7 +738,7 @@ private void UpdateContinueOnError(ItemBucket bucket, TaskHost taskHost) /// The batching bucket /// The task execution mode /// The result of running the task. - private async Task ExecuteInstantiatedTask(TaskExecutionHost taskExecutionHost, TaskLoggingContext taskLoggingContext, TaskHost taskHost, ItemBucket bucket, TaskExecutionMode howToExecuteTask) + private async ValueTask ExecuteInstantiatedTask(TaskExecutionHost taskExecutionHost, TaskLoggingContext taskLoggingContext, TaskHost taskHost, ItemBucket bucket, TaskExecutionMode howToExecuteTask) { UpdateContinueOnError(bucket, taskHost); @@ -859,7 +859,7 @@ private async Task ExecuteInstantiatedTask(TaskExecutionHost tas } else if (type == typeof(ThreadAbortException)) { -#if !NET6_0_OR_GREATER && !NET6_0 // This is redundant but works around https://github.com/dotnet/sdk/issues/20700 +#if !NET Thread.ResetAbort(); #endif _continueOnError = ContinueOnError.ErrorAndStop; diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index 45414d7cf5c..0c4bb721766 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -42,7 +42,7 @@ internal class TaskHost : /// /// Help diagnose tasks that log after they return. /// - private static bool s_breakOnLogAfterTaskReturns = Environment.GetEnvironmentVariable("MSBUILDBREAKONLOGAFTERTASKRETURNS") == "1"; + private static readonly bool s_breakOnLogAfterTaskReturns = Environment.GetEnvironmentVariable("MSBUILDBREAKONLOGAFTERTASKRETURNS") == "1"; /// /// The build component host @@ -357,7 +357,7 @@ public void Yield() { IRequestBuilderCallback builderCallback = _requestEntry.Builder as IRequestBuilderCallback; ErrorUtilities.VerifyThrow(_yieldThreadId == -1, "Cannot call Yield() while yielding."); - _yieldThreadId = Thread.CurrentThread.ManagedThreadId; + _yieldThreadId = Environment.CurrentManagedThreadId; MSBuildEventSource.Log.ExecuteTaskYieldStart(_taskLoggingContext.TaskName, _taskLoggingContext.BuildEventContext.TaskId); builderCallback.Yield(); } @@ -386,7 +386,7 @@ public void Reacquire() { IRequestBuilderCallback builderCallback = _requestEntry.Builder as IRequestBuilderCallback; ErrorUtilities.VerifyThrow(_yieldThreadId != -1, "Cannot call Reacquire() before Yield()."); - ErrorUtilities.VerifyThrow(_yieldThreadId == Thread.CurrentThread.ManagedThreadId, "Cannot call Reacquire() on thread {0} when Yield() was called on thread {1}", Thread.CurrentThread.ManagedThreadId, _yieldThreadId); + ErrorUtilities.VerifyThrow(_yieldThreadId == Environment.CurrentManagedThreadId, "Cannot call Reacquire() on thread {0} when Yield() was called on thread {1}", Environment.CurrentManagedThreadId, _yieldThreadId); MSBuildEventSource.Log.ExecuteTaskYieldStop(_taskLoggingContext.TaskName, _taskLoggingContext.BuildEventContext.TaskId); MSBuildEventSource.Log.ExecuteTaskReacquireStart(_taskLoggingContext.TaskName, _taskLoggingContext.BuildEventContext.TaskId); builderCallback.Reacquire(); diff --git a/src/Build/BackEnd/Components/Scheduler/ScheduleResponse.cs b/src/Build/BackEnd/Components/Scheduler/ScheduleResponse.cs index bc5e8f86256..c335c2cebf1 100644 --- a/src/Build/BackEnd/Components/Scheduler/ScheduleResponse.cs +++ b/src/Build/BackEnd/Components/Scheduler/ScheduleResponse.cs @@ -237,26 +237,26 @@ public override string ToString() { case ScheduleActionType.ReportResults: case ScheduleActionType.ResumeExecution: - return String.Format(CultureInfo.CurrentCulture, "Act: {0} Node: {1} Request: {2}", Action, NodeId, Unblocker.BlockedRequestId); + return $"Act: {Action} Node: {NodeId} Request: {Unblocker.BlockedRequestId}"; case ScheduleActionType.Schedule: - return String.Format(CultureInfo.CurrentCulture, "Act: {0} Node: {1} Request: {2} Parent {3}", Action, NodeId, BuildRequest.GlobalRequestId, BuildRequest.ParentGlobalRequestId); + return $"Act: {Action} Node: {NodeId} Request: {BuildRequest.GlobalRequestId} Parent {BuildRequest.ParentGlobalRequestId}"; case ScheduleActionType.ScheduleWithConfiguration: - return String.Format(CultureInfo.CurrentCulture, "Act: {0} Node: {1} Request: {2} Parent {3} Configuration: {4}", Action, NodeId, BuildRequest.GlobalRequestId, BuildRequest.ParentGlobalRequestId, BuildRequest.ConfigurationId); + return $"Act: {Action} Node: {NodeId} Request: {BuildRequest.GlobalRequestId} Parent {BuildRequest.ParentGlobalRequestId} Configuration: {BuildRequest.ConfigurationId}"; case ScheduleActionType.CircularDependency: - return String.Format(CultureInfo.CurrentCulture, "Act: {0} Node: {1} Request: {2} Parent {3} Configuration: {4}", Action, NodeId, BuildRequest.GlobalRequestId, BuildRequest.ParentGlobalRequestId, BuildRequest.ConfigurationId); + return $"Act: {Action} Node: {NodeId} Request: {BuildRequest.GlobalRequestId} Parent {BuildRequest.ParentGlobalRequestId} Configuration: {BuildRequest.ConfigurationId}"; case ScheduleActionType.SubmissionComplete: - return String.Format(CultureInfo.CurrentCulture, "Act: {0} Submission: {1}", Action, BuildResult.SubmissionId); + return $"Act: {Action} Submission: {BuildResult.SubmissionId}"; case ScheduleActionType.CreateNode: - return String.Format(CultureInfo.CurrentCulture, "Act: {0} Count: {1}", Action, NumberOfNodesToCreate); + return $"Act: {Action} Count: {NumberOfNodesToCreate}"; case ScheduleActionType.NoAction: default: - return String.Format(CultureInfo.CurrentCulture, "Act: {0}", Action); + return $"Act: {Action}"; } } } diff --git a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs index 0e8b74863be..5911b984a72 100644 --- a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs +++ b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs @@ -2431,7 +2431,7 @@ private void WriteNodeUtilizationGraphLine(ILoggingService loggingService, Build bool haveNonIdleNode = false; StringBuilder stringBuilder = new StringBuilder(64); - stringBuilder.AppendFormat("{0}: ", previousEventTime.Ticks); + stringBuilder.Append(previousEventTime.Ticks).Append(": "); for (int i = 0; i < currentWork.Length; i++) { if (currentWork[i] == invalidWorkId) @@ -2567,7 +2567,7 @@ private void TraceScheduler(string format, params object[] stuff) FileUtilities.EnsureDirectoryExists(_debugDumpPath); using StreamWriter file = FileUtilities.OpenWrite(string.Format(CultureInfo.CurrentCulture, Path.Combine(_debugDumpPath, "SchedulerTrace_{0}.txt"), EnvironmentUtilities.CurrentProcessId), append: true); - file.Write("{0}({1})-{2}: ", Thread.CurrentThread.Name, Thread.CurrentThread.ManagedThreadId, _schedulingData.EventTime.Ticks); + file.Write("{0}({1})-{2}: ", Thread.CurrentThread.Name, Environment.CurrentManagedThreadId, _schedulingData.EventTime.Ticks); file.WriteLine(format, stuff); file.Flush(); } @@ -2814,7 +2814,7 @@ private void DumpRequestSpec(StreamWriter file, SchedulableRequest request, int request.State, buildRequest.ConfigurationId, _configCache[buildRequest.ConfigurationId].ProjectFullPath, - string.Join(", ", buildRequest.Targets.ToArray())); + string.Join(", ", buildRequest.Targets)); } /// diff --git a/src/Build/BackEnd/Components/Scheduler/SchedulingPlan.cs b/src/Build/BackEnd/Components/Scheduler/SchedulingPlan.cs index c15b5a25063..19a63a6eb5f 100644 --- a/src/Build/BackEnd/Components/Scheduler/SchedulingPlan.cs +++ b/src/Build/BackEnd/Components/Scheduler/SchedulingPlan.cs @@ -363,9 +363,8 @@ private void DetermineExpensiveConfigs() /// private void ReadHierarchy(StreamReader file) { - while (!file.EndOfStream) + while (file.ReadLine() is string line) { - string line = file.ReadLine(); if (line.Length == 0) { return; @@ -394,9 +393,8 @@ private void ReadHierarchy(StreamReader file) /// private void ReadTimes(StreamReader file) { - while (!file.EndOfStream) + while (file.ReadLine() is string line) { - string line = file.ReadLine(); if (line.Length == 0) { return; diff --git a/src/Build/BackEnd/Components/SdkResolution/SdkResolverService.cs b/src/Build/BackEnd/Components/SdkResolution/SdkResolverService.cs index a9ea6b37548..1d6ec92f64c 100644 --- a/src/Build/BackEnd/Components/SdkResolution/SdkResolverService.cs +++ b/src/Build/BackEnd/Components/SdkResolution/SdkResolverService.cs @@ -136,7 +136,7 @@ public virtual SdkResult ResolveSdk(int submissionId, SdkReference sdk, LoggingC // // Overall, while Sdk resolvers look like a general plug-in system, there are good reasons why some of the logic is hard-coded. // It's not really meant to be modified outside of very special/internal scenarios. -#if NETCOREAPP +#if NET if (ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_10)) { if (TryResolveSdkUsingSpecifiedResolvers( @@ -479,7 +479,7 @@ private void RegisterResolversManifests(ElementLocation location) _manifestToResolvers = new Dictionary>(); SdkResolverManifest sdkDefaultResolversManifest = null; -#if NETCOREAPP +#if NET if (!ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_10)) #endif { diff --git a/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs b/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs index f90f99d14d3..d2c22d49f76 100644 --- a/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs +++ b/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs @@ -869,7 +869,7 @@ public override int GetHashCode() /// String representation of the object public override string ToString() { - return String.Format(CultureInfo.CurrentCulture, "{0} {1} {2} {3}", _configId, _projectFullPath, _toolsVersion, _globalProperties); + return $"{_configId} {_projectFullPath} {_toolsVersion} {_globalProperties}"; } /// diff --git a/src/Build/BuildCheck/Acquisition/BuildCheckAcquisitionModule.cs b/src/Build/BuildCheck/Acquisition/BuildCheckAcquisitionModule.cs index ccedf3bab7f..286e1bf9073 100644 --- a/src/Build/BuildCheck/Acquisition/BuildCheckAcquisitionModule.cs +++ b/src/Build/BuildCheck/Acquisition/BuildCheckAcquisitionModule.cs @@ -40,8 +40,8 @@ public List CreateCheckFactories( assembly = Assembly.LoadFrom(checkAcquisitionData.AssemblyPath); #endif - IList availableTypes = assembly.GetExportedTypes(); - IList checkTypes = availableTypes.Where(t => typeof(Check).IsAssignableFrom(t)).ToArray(); + Type[] availableTypes = assembly.GetExportedTypes(); + Type[] checkTypes = availableTypes.Where(t => typeof(Check).IsAssignableFrom(t)).ToArray(); foreach (Type checkCandidate in checkTypes) { @@ -49,7 +49,7 @@ public List CreateCheckFactories( checkContext.DispatchAsComment(MessageImportance.Normal, "CustomCheckRegistered", checkCandidate.Name, checkCandidate.Assembly); } - if (availableTypes.Count != checkTypes.Count) + if (availableTypes.Length != checkTypes.Length) { availableTypes.Except(checkTypes).ToList() .ForEach(t => checkContext.DispatchAsComment(MessageImportance.Normal, "CustomCheckBaseTypeNotAssignable", t.Name, t.Assembly)); diff --git a/src/Build/BuildCheck/Infrastructure/EditorConfig/EditorConfigFile.cs b/src/Build/BuildCheck/Infrastructure/EditorConfig/EditorConfigFile.cs index e40d33efb34..1039bfa67cc 100644 --- a/src/Build/BuildCheck/Infrastructure/EditorConfig/EditorConfigFile.cs +++ b/src/Build/BuildCheck/Infrastructure/EditorConfig/EditorConfigFile.cs @@ -23,23 +23,16 @@ internal partial class EditorConfigFile // Matches EditorConfig property such as "indent_style = space", see https://editorconfig.org for details private const string s_propertyMatcherPattern = @"^\s*([\w\.\-_]+)\s*[=:]\s*(.*?)\s*([#;].*)?$"; -#if NETCOREAPP - +#if NET [GeneratedRegex(s_sectionMatcherPattern)] - private static partial Regex GetSectionMatcherRegex(); + private static partial Regex SectionMatcherRegex { get; } [GeneratedRegex(s_propertyMatcherPattern)] - private static partial Regex GetPropertyMatcherRegex(); - + private static partial Regex PropertyMatcherRegex { get; } #else - private static readonly Regex s_sectionMatcher = new Regex(s_sectionMatcherPattern, RegexOptions.Compiled); - - private static readonly Regex s_propertyMatcher = new Regex(s_propertyMatcherPattern, RegexOptions.Compiled); - - private static Regex GetSectionMatcherRegex() => s_sectionMatcher; - - private static Regex GetPropertyMatcherRegex() => s_propertyMatcher; + private static Regex SectionMatcherRegex { get; } = new Regex(s_sectionMatcherPattern, RegexOptions.Compiled); + private static Regex PropertyMatcherRegex { get; } = new Regex(s_propertyMatcherPattern, RegexOptions.Compiled); #endif internal Section GlobalSection { get; } @@ -90,12 +83,12 @@ internal static EditorConfigFile Parse(string text) continue; } - var sectionMatches = GetSectionMatcherRegex().Matches(line); - if (sectionMatches.Count > 0 && sectionMatches[0].Groups.Count > 0) + var sectionMatch = SectionMatcherRegex.Match(line); + if (sectionMatch.Success && sectionMatch.Groups.Count > 0) { addNewSection(); - var sectionName = sectionMatches[0].Groups[1].Value; + var sectionName = sectionMatch.Groups[1].Value; Debug.Assert(!string.IsNullOrEmpty(sectionName)); activeSectionName = sectionName; @@ -103,11 +96,11 @@ internal static EditorConfigFile Parse(string text) continue; } - var propMatches = GetPropertyMatcherRegex().Matches(line); - if (propMatches.Count > 0 && propMatches[0].Groups.Count > 1) + var propMatch = PropertyMatcherRegex.Match(line); + if (propMatch.Success && propMatch.Groups.Count > 1) { - var key = propMatches[0].Groups[1].Value.ToLower(); - var value = propMatches[0].Groups[2].Value; + var key = propMatch.Groups[1].Value.ToLower(); + var value = propMatch.Groups[2].Value; Debug.Assert(!string.IsNullOrEmpty(key)); Debug.Assert(key == key.Trim()); diff --git a/src/Build/BuildCheck/Infrastructure/EditorConfig/EditorConfigParser.cs b/src/Build/BuildCheck/Infrastructure/EditorConfig/EditorConfigParser.cs index 56b0842acf7..393a9f7612c 100644 --- a/src/Build/BuildCheck/Infrastructure/EditorConfig/EditorConfigParser.cs +++ b/src/Build/BuildCheck/Infrastructure/EditorConfig/EditorConfigParser.cs @@ -41,12 +41,7 @@ internal List DiscoverEditorConfigFiles(string filePath) { var editorConfig = _editorConfigFileCache.GetOrAdd(editorConfigFilePath, (key) => { - using (FileStream stream = new FileStream(editorConfigFilePath, FileMode.Open, System.IO.FileAccess.Read, FileShare.Read)) - { - using StreamReader sr = new StreamReader(editorConfigFilePath); - var editorConfigfileContent = sr.ReadToEnd(); - return EditorConfigFile.Parse(editorConfigfileContent); - } + return EditorConfigFile.Parse(File.ReadAllText(editorConfigFilePath)); }); editorConfigDataFromFilesList.Add(editorConfig); diff --git a/src/Build/Collections/CopyOnReadEnumerable.cs b/src/Build/Collections/CopyOnReadEnumerable.cs index 6ed48e0eed1..056f5804cfd 100644 --- a/src/Build/Collections/CopyOnReadEnumerable.cs +++ b/src/Build/Collections/CopyOnReadEnumerable.cs @@ -62,7 +62,7 @@ public IEnumerator GetEnumerator() { List list; -#if NETCOREAPP +#if NET if (_backingEnumerable.TryGetNonEnumeratedCount(out int count)) { #else diff --git a/src/Build/Construction/ProjectElementContainer.cs b/src/Build/Construction/ProjectElementContainer.cs index c8fabf3559e..6c6584e52c7 100644 --- a/src/Build/Construction/ProjectElementContainer.cs +++ b/src/Build/Construction/ProjectElementContainer.cs @@ -584,7 +584,7 @@ private static string GetElementIndentation(XmlElementWithLocation xmlElement) var leadingWhiteSpace = xmlElement.PreviousSibling.Value; - var lastIndexOfNewLine = leadingWhiteSpace.LastIndexOf("\n", StringComparison.Ordinal); + var lastIndexOfNewLine = leadingWhiteSpace.LastIndexOf('\n'); if (lastIndexOfNewLine == -1) { diff --git a/src/Build/Construction/ProjectRootElement.cs b/src/Build/Construction/ProjectRootElement.cs index eee48e9f667..903de887aac 100644 --- a/src/Build/Construction/ProjectRootElement.cs +++ b/src/Build/Construction/ProjectRootElement.cs @@ -43,7 +43,7 @@ namespace Microsoft.Build.Construction /// to control its lifetime and not be surprised by edits via another project collection. /// [DebuggerDisplay("{FullPath} #Children={Count} DefaultTargets={DefaultTargets} ToolsVersion={ToolsVersion} InitialTargets={InitialTargets} ExplicitlyLoaded={IsExplicitlyLoaded}")] - public class ProjectRootElement : ProjectElementContainer + public partial class ProjectRootElement : ProjectElementContainer { // Constants for default (empty) project file. private const string EmptyProjectFileContent = "{0}\r\n"; @@ -58,10 +58,18 @@ public class ProjectRootElement : ProjectElementContainer private static readonly ProjectRootElementCacheBase.OpenProjectRootElement s_openLoaderPreserveFormattingDelegate = OpenLoaderPreserveFormatting; + private const string XmlDeclarationPattern = @"\A\s*\<\?\s*xml.*\?\>\s*\Z"; + /// /// Used to determine if a file is an empty XML file if it ONLY contains an XML declaration like <?xml version="1.0" encoding="utf-8"?>. /// - private static readonly Lazy XmlDeclarationRegEx = new Lazy(() => new Regex(@"\A\s*\<\?\s*xml.*\?\>\s*\Z"), isThreadSafe: true); +#if NET + [GeneratedRegex(XmlDeclarationPattern)] + private static partial Regex XmlDeclarationRegex { get; } +#else + private static Regex XmlDeclarationRegex => s_xmlDeclarationRegex ??= new Regex(XmlDeclarationPattern); + private static Regex s_xmlDeclarationRegex; +#endif /// /// The default encoding to use / assume for a new project. @@ -1988,9 +1996,9 @@ internal static bool IsEmptyXmlFile(string path) string contents = File.ReadAllText(path); - // If the file is only whitespace or the XML declaration then it empty + // If the file is only whitespace or the XML declaration then it is empty // - return String.IsNullOrEmpty(contents) || XmlDeclarationRegEx.Value.IsMatch(contents); + return String.IsNullOrEmpty(contents) || XmlDeclarationRegex.IsMatch(contents); } catch (Exception) { diff --git a/src/Build/Construction/ProjectTargetElement.cs b/src/Build/Construction/ProjectTargetElement.cs index db079f86773..4fdf9c21495 100644 --- a/src/Build/Construction/ProjectTargetElement.cs +++ b/src/Build/Construction/ProjectTargetElement.cs @@ -106,7 +106,7 @@ public string Name string unescapedValue = EscapingUtilities.UnescapeAll(value); - int indexOfSpecialCharacter = unescapedValue.IndexOfAny(XMakeElements.InvalidTargetNameCharacters); + int indexOfSpecialCharacter = unescapedValue.AsSpan().IndexOfAny(XMakeElements.InvalidTargetNameCharacters); if (indexOfSpecialCharacter >= 0) { ErrorUtilities.ThrowArgument("OM_NameInvalid", unescapedValue, unescapedValue[indexOfSpecialCharacter]); diff --git a/src/Build/Construction/Solution/ProjectInSolution.cs b/src/Build/Construction/Solution/ProjectInSolution.cs index 60f69b97b54..4eb3b5e82bd 100644 --- a/src/Build/Construction/Solution/ProjectInSolution.cs +++ b/src/Build/Construction/Solution/ProjectInSolution.cs @@ -2,9 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Buffers; using System.Collections; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; +using System.Linq; using System.Security; using System.Text; using System.Xml; @@ -16,8 +19,6 @@ using ProjectFileErrorUtilities = Microsoft.Build.Shared.ProjectFileErrorUtilities; using BuildEventFileInfo = Microsoft.Build.Shared.BuildEventFileInfo; using ErrorUtilities = Microsoft.Build.Shared.ErrorUtilities; -using System.Collections.ObjectModel; -using System.Linq; #nullable disable @@ -83,7 +84,12 @@ public sealed class ProjectInSolution /// /// Characters that need to be cleansed from a project name. /// - private static readonly char[] s_charsToCleanse = { '%', '$', '@', ';', '.', '(', ')', '\'' }; +#if NET + private static readonly SearchValues s_charsToCleanse = SearchValues.Create( +#else + private static readonly char[] s_charsToCleanse = ( +#endif + ['%', '$', '@', ';', '.', '(', ')', '\'']); /// /// Project names that need to be disambiguated when forming a target name @@ -501,12 +507,25 @@ private static string CleanseProjectName(string projectName) // If there are no special chars, just return the original string immediately. // Don't even instantiate the StringBuilder. - int indexOfChar = projectName.IndexOfAny(s_charsToCleanse); + int indexOfChar = projectName.AsSpan().IndexOfAny(s_charsToCleanse); if (indexOfChar == -1) { return projectName; } +#if NET + return string.Create(projectName.Length, (projectName, indexOfChar), static (dest, state) => + { + state.projectName.AsSpan().CopyTo(dest); + int pos = state.indexOfChar; + do + { + dest[pos] = cleanCharacter; + dest = dest.Slice(pos + 1); + } + while ((pos = dest.IndexOfAny(s_charsToCleanse)) >= 0); + }); +#else // This is where we're going to work on the final string to return to the caller. var cleanProjectName = new StringBuilder(projectName); @@ -517,6 +536,7 @@ private static string CleanseProjectName(string projectName) } return cleanProjectName.ToString(); +#endif } /// @@ -561,7 +581,7 @@ private static bool ElementContainsInvalidNamespaceDefitions(XmlElement mainProj return false; } - #endregion +#endregion #region Constants diff --git a/src/Build/Construction/Solution/SolutionFile.cs b/src/Build/Construction/Solution/SolutionFile.cs index 3a7a0ce42a7..01ece85d306 100644 --- a/src/Build/Construction/Solution/SolutionFile.cs +++ b/src/Build/Construction/Solution/SolutionFile.cs @@ -35,38 +35,47 @@ namespace Microsoft.Build.Construction /// This class contains the functionality to parse a solution file and return a corresponding /// MSBuild project file containing the projects and dependencies defined in the solution. /// - public sealed class SolutionFile + public sealed partial class SolutionFile { #region Solution specific constants // An example of a project line looks like this: // Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClassLibrary1", "ClassLibrary1\ClassLibrary1.csproj", "{05A5AD00-71B5-4612-AF2F-9EA9121C4111}" - private static readonly Lazy s_crackProjectLine = new Lazy( - () => new Regex( - "^" // Beginning of line - + "Project\\(\"(?.*)\"\\)" - + "\\s*=\\s*" // Any amount of whitespace plus "=" plus any amount of whitespace - + "\"(?.*)\"" - + "\\s*,\\s*" // Any amount of whitespace plus "," plus any amount of whitespace - + "\"(?.*)\"" - + "\\s*,\\s*" // Any amount of whitespace plus "," plus any amount of whitespace - + "\"(?.*)\"" - + "$", // End-of-line - RegexOptions.Compiled)); + private const string CrackProjectLinePattern = + "^" // Beginning of line + + "Project\\(\"(?.*)\"\\)" + + "\\s*=\\s*" // Any amount of whitespace plus "=" plus any amount of whitespace + + "\"(?.*)\"" + + "\\s*,\\s*" // Any amount of whitespace plus "," plus any amount of whitespace + + "\"(?.*)\"" + + "\\s*,\\s*" // Any amount of whitespace plus "," plus any amount of whitespace + + "\"(?.*)\"" + + "$"; // End-of-line // An example of a property line looks like this: // AspNetCompiler.VirtualPath = "/webprecompile" // Because website projects now include the target framework moniker as // one of their properties, may now have '=' in it. - - private static readonly Lazy s_crackPropertyLine = new Lazy( - () => new Regex( - "^" // Beginning of line - + "(?[^=]*)" - + "\\s*=\\s*" // Any amount of whitespace plus "=" plus any amount of whitespace - + "(?.*)" - + "$", // End-of-line - RegexOptions.Compiled)); + private const string CrackPropertyLinePattern = + "^" // Beginning of line + + "(?[^=]*)" + + "\\s*=\\s*" // Any amount of whitespace plus "=" plus any amount of whitespace + + "(?.*)" + + "$"; // End-of-line + +#if NET + [GeneratedRegex(CrackProjectLinePattern)] + private static partial Regex CrackProjectLineRegex { get; } + + [GeneratedRegex(CrackPropertyLinePattern)] + private static partial Regex CrackPropertyLineRegex { get; } +#else + private static Regex CrackProjectLineRegex => s_crackProjectLineRegex ??= new Regex(CrackProjectLinePattern, RegexOptions.Compiled); + private static Regex CrackPropertyLineRegex => s_crackPropertyLineRegex ??= new Regex(CrackPropertyLinePattern, RegexOptions.Compiled); + + private static Regex s_crackProjectLineRegex; + private static Regex s_crackPropertyLineRegex; +#endif internal const int slnFileMinUpgradableVersion = 7; // Minimum version for MSBuild to give a nice message internal const int slnFileMinVersion = 9; // Minimum version for MSBuild to actually do anything useful @@ -89,7 +98,7 @@ public sealed class SolutionFile private const string sharedProjectGuid = "{D954291E-2A0B-460D-934E-DC6B0785DB48}"; private const char CommentStartChar = '#'; - #endregion +#endregion #region Member data private string _solutionFile; // Could be absolute or relative path to the .SLN file. private string _solutionFilterFile; // Could be absolute or relative path to the .SLNF file. @@ -121,7 +130,7 @@ public sealed class SolutionFile // TODO: Unify to NativeMethodsShared.OSUsesCaseSensitive paths // when possible. - private static StringComparer _pathComparer = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) + private static readonly StringComparer _pathComparer = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase; @@ -561,7 +570,12 @@ internal static void GetSolutionFileAndVisualStudioMajorVersions(string solution if (line.Trim().StartsWith(slnFileHeaderNoVersion, StringComparison.Ordinal)) { // Found it. Validate the version. - string fileVersionFromHeader = line.Substring(slnFileHeaderNoVersion.Length); + var fileVersionFromHeader = +#if NET + line.AsSpan(slnFileHeaderNoVersion.Length); +#else + line.Substring(slnFileHeaderNoVersion.Length); +#endif if (!System.Version.TryParse(fileVersionFromHeader, out Version version)) { @@ -1061,7 +1075,7 @@ private void ParseProject(string firstLine) { // This should be a dependency. The GUID identifying the parent project should // be both the property name and the property value. - Match match = s_crackPropertyLine.Value.Match(line); + Match match = CrackPropertyLineRegex.Match(line); ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(match.Success, "SubCategoryForSolutionParsingErrors", new BuildEventFileInfo(FullPath, _currentLineNumber, 0), "SolutionParseProjectDepGuidError", proj.ProjectName); @@ -1079,7 +1093,7 @@ private void ParseProject(string firstLine) line = ReadLine(); while ((line?.StartsWith("EndProjectSection", StringComparison.Ordinal) == false)) { - Match match = s_crackPropertyLine.Value.Match(line); + Match match = CrackPropertyLineRegex.Match(line); ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(match.Success, "SubCategoryForSolutionParsingErrors", new BuildEventFileInfo(FullPath, _currentLineNumber, 0), "SolutionParseWebProjectPropertiesError", proj.ProjectName); @@ -1290,7 +1304,7 @@ private void ValidateProjectRelativePath(ProjectInSolution proj) ErrorUtilities.VerifyThrow(proj.RelativePath != null, "Project relative path cannot be null."); // Verify the relative path does not contain invalid characters - ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(proj.RelativePath.IndexOfAny(Path.GetInvalidPathChars()) == -1, + ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(proj.RelativePath.AsSpan().IndexOfAny(MSBuildConstants.InvalidPathChars) < 0, "SubCategoryForSolutionParsingErrors", new BuildEventFileInfo(FullPath, _currentLineNumber, 0), "SolutionParseInvalidProjectFileNameCharacters", @@ -1357,7 +1371,13 @@ private static void ParseAspNetCompilerProperty( string configurationName = propertyName.Substring(0, indexOfFirstDot); // The rest of it is the actual property name. - string aspNetPropertyName = ((propertyName.Length - indexOfFirstDot) > 0) ? propertyName.Substring(indexOfFirstDot + 1, propertyName.Length - indexOfFirstDot - 1) : ""; + var aspNetPropertyName = ((propertyName.Length - indexOfFirstDot) > 0) ? +#if NET + propertyName.AsSpan(indexOfFirstDot + 1) : +#else + propertyName.Substring(indexOfFirstDot + 1) : +#endif + ""; // And the part after the sign is the property value (which was parsed out for us prior // to calling this method). @@ -1392,49 +1412,19 @@ private static void ParseAspNetCompilerProperty( } // Update the appropriate field within the parameters struct. - if (aspNetPropertyName == "AspNetCompiler.VirtualPath") - { - aspNetCompilerParameters.aspNetVirtualPath = propertyValue; - } - else if (aspNetPropertyName == "AspNetCompiler.PhysicalPath") - { - aspNetCompilerParameters.aspNetPhysicalPath = propertyValue; - } - else if (aspNetPropertyName == "AspNetCompiler.TargetPath") - { - aspNetCompilerParameters.aspNetTargetPath = propertyValue; - } - else if (aspNetPropertyName == "AspNetCompiler.ForceOverwrite") - { - aspNetCompilerParameters.aspNetForce = propertyValue; - } - else if (aspNetPropertyName == "AspNetCompiler.Updateable") - { - aspNetCompilerParameters.aspNetUpdateable = propertyValue; - } - else if (aspNetPropertyName == "AspNetCompiler.Debug") + switch (aspNetPropertyName) { - aspNetCompilerParameters.aspNetDebug = propertyValue; - } - else if (aspNetPropertyName == "AspNetCompiler.KeyFile") - { - aspNetCompilerParameters.aspNetKeyFile = propertyValue; - } - else if (aspNetPropertyName == "AspNetCompiler.KeyContainer") - { - aspNetCompilerParameters.aspNetKeyContainer = propertyValue; - } - else if (aspNetPropertyName == "AspNetCompiler.DelaySign") - { - aspNetCompilerParameters.aspNetDelaySign = propertyValue; - } - else if (aspNetPropertyName == "AspNetCompiler.AllowPartiallyTrustedCallers") - { - aspNetCompilerParameters.aspNetAPTCA = propertyValue; - } - else if (aspNetPropertyName == "AspNetCompiler.FixedNames") - { - aspNetCompilerParameters.aspNetFixedNames = propertyValue; + case "AspNetCompiler.VirtualPath": aspNetCompilerParameters.aspNetVirtualPath = propertyValue; break; + case "AspNetCompiler.PhysicalPath": aspNetCompilerParameters.aspNetPhysicalPath = propertyValue; break; + case "AspNetCompiler.TargetPath": aspNetCompilerParameters.aspNetTargetPath = propertyValue; break; + case "AspNetCompiler.ForceOverwrite": aspNetCompilerParameters.aspNetForce = propertyValue; break; + case "AspNetCompiler.Updateable": aspNetCompilerParameters.aspNetUpdateable = propertyValue; break; + case "AspNetCompiler.Debug": aspNetCompilerParameters.aspNetDebug = propertyValue; break; + case "AspNetCompiler.KeyFile": aspNetCompilerParameters.aspNetKeyFile = propertyValue; break; + case "AspNetCompiler.KeyContainer": aspNetCompilerParameters.aspNetKeyContainer = propertyValue; break; + case "AspNetCompiler.DelaySign": aspNetCompilerParameters.aspNetDelaySign = propertyValue; break; + case "AspNetCompiler.AllowPartiallyTrustedCallers": aspNetCompilerParameters.aspNetAPTCA = propertyValue; break; + case "AspNetCompiler.FixedNames": aspNetCompilerParameters.aspNetFixedNames = propertyValue; break; } // Store the updated parameters struct back into the hashtable by configuration name. @@ -1511,7 +1501,7 @@ internal void ParseFirstProjectLine( string firstLine, ProjectInSolution proj) { - Match match = s_crackProjectLine.Value.Match(firstLine); + Match match = CrackProjectLineRegex.Match(firstLine); ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(match.Success, "SubCategoryForSolutionParsingErrors", new BuildEventFileInfo(FullPath, _currentLineNumber, 0), "SolutionParseProjectError"); @@ -1524,7 +1514,7 @@ internal void ParseFirstProjectLine( // This allows us to at least generate reasonable target names etc. instead of crashing. if (String.IsNullOrEmpty(proj.ProjectName)) { - proj.ProjectName = "EmptyProjectName." + Guid.NewGuid(); + proj.ProjectName = $"EmptyProjectName.{Guid.NewGuid()}"; } // Validate project relative path @@ -1611,7 +1601,7 @@ internal void ParseNestedProjects() continue; } - Match match = s_crackPropertyLine.Value.Match(str); + Match match = CrackPropertyLineRegex.Match(str); ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(match.Success, "SubCategoryForSolutionParsingErrors", new BuildEventFileInfo(FullPath, _currentLineNumber, 0), "SolutionParseNestedProjectError"); @@ -1770,16 +1760,14 @@ internal void ProcessProjectConfigurationSection(Dictionary rawP { // The "ActiveCfg" entry defines the active project configuration in the given solution configuration // This entry must be present for every possible solution configuration/project combination. - string entryNameActiveConfig = string.Format(CultureInfo.InvariantCulture, "{0}.{1}.ActiveCfg", - project.ProjectGuid, solutionConfiguration.FullName); + string entryNameActiveConfig = $"{project.ProjectGuid}.{solutionConfiguration.FullName}.ActiveCfg"; // The "Build.0" entry tells us whether to build the project configuration in the given solution configuration. // Technically, it specifies a configuration name of its own which seems to be a remnant of an initial, // more flexible design of solution configurations (as well as the '.0' suffix - no higher values are ever used). // The configuration name is not used, and the whole entry means "build the project configuration" // if it's present in the solution file, and "don't build" if it's not. - string entryNameBuild = string.Format(CultureInfo.InvariantCulture, "{0}.{1}.Build.0", - project.ProjectGuid, solutionConfiguration.FullName); + string entryNameBuild = $"{project.ProjectGuid}.{solutionConfiguration.FullName}.Build.0"; if (rawProjectConfigurationsEntries.TryGetValue(entryNameActiveConfig, out string configurationPlatform)) { @@ -1905,6 +1893,6 @@ internal string GetProjectRelativePathByGuid(string projectGuid) return null; } - #endregion +#endregion } // class SolutionFile } // namespace Microsoft.Build.Construction diff --git a/src/Build/Construction/Solution/SolutionProjectGenerator.cs b/src/Build/Construction/Solution/SolutionProjectGenerator.cs index d535d1c73c0..e51789bbb38 100644 --- a/src/Build/Construction/Solution/SolutionProjectGenerator.cs +++ b/src/Build/Construction/Solution/SolutionProjectGenerator.cs @@ -860,7 +860,7 @@ private ProjectInstance CreateTraversalInstance(string wrapperProjectToolsVersio traversalProject.ToolsVersion = wrapperProjectToolsVersion; traversalProject.DefaultTargets = "Build"; traversalProject.InitialTargets = "ValidateSolutionConfiguration;ValidateToolsVersions;ValidateProjects"; - traversalProject.FullPath = _solutionFile.FullPath + ".metaproj"; + traversalProject.FullPath = $"{_solutionFile.FullPath}.metaproj"; // Add default solution configuration/platform names in case the user doesn't specify them on the command line AddConfigurationPlatformDefaults(traversalProject); @@ -982,7 +982,7 @@ private ProjectInstance CreateTraversalInstance(string wrapperProjectToolsVersio // For debugging purposes: some information is lost when evaluating into a project instance, // so make it possible to see what we have at this point. string path = traversalProject.FullPath; - string metaprojectPath = _solutionFile.FullPath + ".metaproj.tmp"; + string metaprojectPath = $"{_solutionFile.FullPath}.metaproj.tmp"; EmitMetaproject(traversalProject, metaprojectPath); traversalProject.FullPath = path; @@ -1349,7 +1349,7 @@ private static void AddMetaprojectTargetForManagedProject(ProjectInstance traver string outputItemAsItem = null; if (!String.IsNullOrEmpty(outputItem)) { - outputItemAsItem = "@(" + outputItem + ")"; + outputItemAsItem = $"@({outputItem})"; } ProjectTargetInstance target = metaprojectInstance.AddTarget(targetName ?? "Build", String.Empty, String.Empty, outputItemAsItem, null, String.Empty, String.Empty, String.Empty, String.Empty, false /* legacy target returns behaviour */); @@ -1396,7 +1396,7 @@ private static void AddProjectBuildTask(ProjectInstance traversalProject, Projec /// private void AddMetaprojectBuildTask(ProjectInSolution project, ProjectTargetInstance target, string targetToBuild, string outputItem) { - ProjectTaskInstance task = target.AddTask("MSBuild", Strings.WeakIntern("'%(ProjectReference.Identity)' == '" + GetMetaprojectName(project) + "'"), String.Empty); + ProjectTaskInstance task = target.AddTask("MSBuild", Strings.WeakIntern($"'%(ProjectReference.Identity)' == '{GetMetaprojectName(project)}'"), String.Empty); task.SetParameter("Projects", "@(ProjectReference)"); if (targetToBuild != null) @@ -1979,7 +1979,7 @@ private static void AddTraversalReferencesTarget(ProjectInstance traversalProjec string outputItemAsItem = null; if (!String.IsNullOrEmpty(outputItem)) { - outputItemAsItem = "@(" + outputItem + ")"; + outputItemAsItem = $"@({outputItem})"; } string correctedTargetName = targetName ?? "Build"; @@ -2052,13 +2052,13 @@ private void AddTraversalTargetForProject(ProjectInstance traversalProject, Proj if (!String.IsNullOrEmpty(outputItem)) { outputItemName = MakeIntoSafeItemName(baseProjectName) + outputItem; - outputItemAsItem = "@(" + outputItemName + ")"; + outputItemAsItem = $"@({outputItemName})"; } ProjectTargetInstance targetElement = traversalProject.AddTarget(actualTargetName, null, null, outputItemAsItem, null, null, null, null, null, false /* legacy target returns behaviour */); if (canBuildDirectly) { - AddProjectBuildTask(traversalProject, projectConfiguration, targetElement, targetToBuild, "@(ProjectReference)", "'%(ProjectReference.Identity)' == '" + EscapingUtilities.Escape(project.AbsolutePath) + "'", outputItemName); + AddProjectBuildTask(traversalProject, projectConfiguration, targetElement, targetToBuild, "@(ProjectReference)", $"'%(ProjectReference.Identity)' == '{EscapingUtilities.Escape(project.AbsolutePath)}'", outputItemName); } else { diff --git a/src/Build/Definition/Project.cs b/src/Build/Definition/Project.cs index 2401d4935a1..23b235e5ad0 100644 --- a/src/Build/Definition/Project.cs +++ b/src/Build/Definition/Project.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Buffers; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; @@ -56,7 +57,12 @@ public class Project : ILinkableObject /// /// * and ? are invalid file name characters, but they occur in globs as wild cards. /// - private static readonly char[] s_invalidGlobChars = FileUtilities.InvalidFileNameChars.Where(c => c != '*' && c != '?' && c != '/' && c != '\\' && c != ':').ToArray(); +#if NET + private static readonly SearchValues s_invalidGlobChars = SearchValues.Create( +#else + private static readonly char[] s_invalidGlobChars = ( +#endif + FileUtilities.InvalidFileNameCharsArray.Where(c => c is not ('*' or '?' or '/' or '\\' or ':')).ToArray()); /// /// Context to log messages and events in. @@ -2618,7 +2624,7 @@ private GlobResult BuildGlobResultFromIncludeItem(ProjectItemElement itemElement { var includeItemspec = new EvaluationItemSpec(itemElement.Include, _data.Expander, itemElement.IncludeLocation, itemElement.ContainingProject.DirectoryPath); - ImmutableArray includeGlobFragments = includeItemspec.Fragments.Where(f => f is GlobFragment && f.TextFragment.IndexOfAny(s_invalidGlobChars) == -1).ToImmutableArray(); + ItemSpecFragment[] includeGlobFragments = includeItemspec.Fragments.Where(f => f is GlobFragment && f.TextFragment.AsSpan().IndexOfAny(s_invalidGlobChars) < 0).ToArray(); if (includeGlobFragments.Length == 0) { return null; diff --git a/src/Build/Definition/ToolsetConfigurationReader.cs b/src/Build/Definition/ToolsetConfigurationReader.cs index eb461785d00..204de4952f8 100644 --- a/src/Build/Definition/ToolsetConfigurationReader.cs +++ b/src/Build/Definition/ToolsetConfigurationReader.cs @@ -207,7 +207,7 @@ protected override IEnumerable GetSubToolsetPropertyD protected override Dictionary GetProjectImportSearchPathsTable(string toolsVersion, string os) { Dictionary kindToPathsCache; - var key = toolsVersion + ":" + os; + var key = $"{toolsVersion}:{os}"; if (_projectImportSearchPathsCache.TryGetValue(key, out kindToPathsCache)) { return kindToPathsCache; diff --git a/src/Build/Definition/ToolsetReader.cs b/src/Build/Definition/ToolsetReader.cs index fdc817b930c..2f3ca882084 100644 --- a/src/Build/Definition/ToolsetReader.cs +++ b/src/Build/Definition/ToolsetReader.cs @@ -686,22 +686,26 @@ private MSBuildExtensionsPathReferenceKind(string value) /// public static MSBuildExtensionsPathReferenceKind FindIn(string expression) { - if (expression.IndexOf("$(MSBuildExtensionsPath)") >= 0) + const string PathBase = "$(MSBuildExtensionsPath"; + int pos = expression.IndexOf(PathBase, StringComparison.Ordinal); + if (pos >= 0) { - return MSBuildExtensionsPathReferenceKind.Default; - } - - if (expression.IndexOf("$(MSBuildExtensionsPath32)") >= 0) - { - return MSBuildExtensionsPathReferenceKind.Path32; - } - - if (expression.IndexOf("$(MSBuildExtensionsPath64)") >= 0) - { - return MSBuildExtensionsPathReferenceKind.Path64; + ReadOnlySpan remainder = expression.AsSpan(pos + PathBase.Length); + if (remainder.StartsWith(")".AsSpan())) + { + return Default; + } + else if (remainder.StartsWith("32)".AsSpan())) + { + return Path32; + } + else if (remainder.StartsWith("64)".AsSpan())) + { + return Path64; + } } - return MSBuildExtensionsPathReferenceKind.None; + return None; } } } diff --git a/src/Build/ElementLocation/ElementLocation.cs b/src/Build/ElementLocation/ElementLocation.cs index 8ca5594c946..29879ac6180 100644 --- a/src/Build/ElementLocation/ElementLocation.cs +++ b/src/Build/ElementLocation/ElementLocation.cs @@ -25,7 +25,7 @@ public abstract class ElementLocation : IElementLocation, ITranslatable, IImmuta /// /// The singleton empty element location. /// - private static ElementLocation s_emptyElementLocation = new SmallElementLocation(null, 0, 0); + private static readonly ElementLocation s_emptyElementLocation = new SmallElementLocation(null, 0, 0); /// /// The file from which this particular element originated. It may @@ -214,7 +214,7 @@ private static string GetLocationString(string file, int line, int column) } else if (line != 0) { - locationString = file + " (" + line + ")"; + locationString = $"{file} ({line})"; } else { diff --git a/src/Build/ElementLocation/XmlDocumentWithLocation.cs b/src/Build/ElementLocation/XmlDocumentWithLocation.cs index cfa910da6a8..eb8c3356873 100644 --- a/src/Build/ElementLocation/XmlDocumentWithLocation.cs +++ b/src/Build/ElementLocation/XmlDocumentWithLocation.cs @@ -27,7 +27,7 @@ internal class XmlDocumentWithLocation : XmlDocument /// /// Used to cache tag names in loaded files. /// - private static NameTable s_nameTable = new XmlNameTableThreadSafe(); + private static readonly NameTable s_nameTable = new XmlNameTableThreadSafe(); /// /// Whether we can selectively load as read-only (eg just when in program files directory) diff --git a/src/Build/Evaluation/ConditionEvaluator.cs b/src/Build/Evaluation/ConditionEvaluator.cs index 67455f8b4c4..f6a5f70330f 100644 --- a/src/Build/Evaluation/ConditionEvaluator.cs +++ b/src/Build/Evaluation/ConditionEvaluator.cs @@ -166,7 +166,7 @@ public ConcurrentStack GetOrAdd(string condition, Func s_cachedExpressionTrees = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary s_cachedExpressionTrees = new ConcurrentDictionary(); /// /// For debugging leaks, a way to disable caching expression trees, to reduce noise diff --git a/src/Build/Evaluation/Conditionals/CharacterUtilities.cs b/src/Build/Evaluation/Conditionals/CharacterUtilities.cs index 06e7345b1b0..ed5502e91ca 100644 --- a/src/Build/Evaluation/Conditionals/CharacterUtilities.cs +++ b/src/Build/Evaluation/Conditionals/CharacterUtilities.cs @@ -22,15 +22,9 @@ internal static bool IsSimpleStringChar(char candidate) return IsSimpleStringStart(candidate) || char.IsDigit(candidate); } - internal static bool IsHexAlphabetic(char candidate) - { - return candidate == 'a' || candidate == 'b' || candidate == 'c' || candidate == 'd' || candidate == 'e' || candidate == 'f' || - candidate == 'A' || candidate == 'B' || candidate == 'C' || candidate == 'D' || candidate == 'E' || candidate == 'F'; - } - internal static bool IsHexDigit(char candidate) { - return char.IsDigit(candidate) || IsHexAlphabetic(candidate); + return char.IsDigit(candidate) || ((uint)((candidate | 0x20) - 'a') <= 'f' - 'a'); } } } diff --git a/src/Build/Evaluation/Conditionals/Scanner.cs b/src/Build/Evaluation/Conditionals/Scanner.cs index a9d31698a28..718ae73d23d 100644 --- a/src/Build/Evaluation/Conditionals/Scanner.cs +++ b/src/Build/Evaluation/Conditionals/Scanner.cs @@ -692,11 +692,11 @@ private bool ParseRemaining() private bool ParseSimpleStringOrFunction(int start) { SkipSimpleStringChars(); - if (string.Equals(_expression.Substring(start, _parsePoint - start), "and", StringComparison.OrdinalIgnoreCase)) + if (_expression.AsSpan(start, _parsePoint - start).Equals("and".AsSpan(), StringComparison.OrdinalIgnoreCase)) { _lookahead = Token.And; } - else if (string.Equals(_expression.Substring(start, _parsePoint - start), "or", StringComparison.OrdinalIgnoreCase)) + else if (_expression.AsSpan(start, _parsePoint - start).Equals("or".AsSpan(), StringComparison.OrdinalIgnoreCase)) { _lookahead = Token.Or; } diff --git a/src/Build/Evaluation/Evaluator.cs b/src/Build/Evaluation/Evaluator.cs index cba4152a3fc..598c949ec54 100644 --- a/src/Build/Evaluation/Evaluator.cs +++ b/src/Build/Evaluation/Evaluator.cs @@ -809,7 +809,7 @@ private void Evaluate() { if (!String.Equals(entry.Name, "currentsolutionconfigurationcontents", StringComparison.OrdinalIgnoreCase)) { - propertyDump += entry.Name + "=" + entry.EvaluatedValue + "\n"; + propertyDump += $"{entry.Name}={entry.EvaluatedValue}\n"; } } @@ -1886,19 +1886,17 @@ static string EvaluateProperty(string value, IElementLocation location, // Creates a project to set the properties and include the items from an SdkResult private ProjectRootElement CreateProjectForSdkResult(SdkResult sdkResult) { - int propertiesAndItemsHash; - -#if NETCOREAPP - HashCode hash = new HashCode(); +#if NET + HashCode hash = default; #else - propertiesAndItemsHash = -849885975; + int propertiesAndItemsHash = -849885975; #endif if (sdkResult.PropertiesToAdd != null) { foreach (var property in sdkResult.PropertiesToAdd) { -#if NETCOREAPP +#if NET hash.Add(property.Key); hash.Add(property.Value); #else @@ -1911,7 +1909,7 @@ private ProjectRootElement CreateProjectForSdkResult(SdkResult sdkResult) { foreach (var item in sdkResult.ItemsToAdd) { -#if NETCOREAPP +#if NET hash.Add(item.Key); hash.Add(item.Value); #else @@ -1922,12 +1920,12 @@ private ProjectRootElement CreateProjectForSdkResult(SdkResult sdkResult) } } -#if NETCOREAPP - propertiesAndItemsHash = hash.ToHashCode(); +#if NET + int propertiesAndItemsHash = hash.ToHashCode(); #endif // Generate a unique filename for the generated project for each unique set of properties and items. - string projectPath = _projectRootElement.FullPath + ".SdkResolver." + propertiesAndItemsHash + ".proj"; + string projectPath = $"{_projectRootElement.FullPath}.SdkResolver.{propertiesAndItemsHash}.proj"; ProjectRootElement InnerCreate(string _, ProjectRootElementCacheBase __) { @@ -2136,7 +2134,7 @@ private LoadImportsResult ExpandAndLoadImportsFromUnescapedImportExpression(stri // If neither file involved is the project itself, append its path in square brackets if (previouslyImportedAt.ContainingProject != _projectRootElement && importElement.ContainingProject != _projectRootElement) { - parenthesizedProjectLocation = "[" + _projectRootElement.FullPath + "]"; + parenthesizedProjectLocation = $"[{_projectRootElement.FullPath}]"; } // TODO: Detect if the duplicate import came from an SDK attribute _evaluationLoggingContext.LogWarning(null, new BuildEventFileInfo(importLocationInProject), "DuplicateImport", importFileUnescaped, previouslyImportedAt.Location.LocationString, parenthesizedProjectLocation); @@ -2577,7 +2575,7 @@ private void SetAllProjectsProperty() if (_lastModifiedProject != null) { P oldValue = _data.GetProperty(Constants.MSBuildAllProjectsPropertyName); - string streamImports = string.Join(";", _streamImports.ToArray()); + string streamImports = string.Join(";", _streamImports); _data.SetProperty( Constants.MSBuildAllProjectsPropertyName, oldValue == null diff --git a/src/Build/Evaluation/Expander.cs b/src/Build/Evaluation/Expander.cs index 84b34d5c67a..7397dee024f 100644 --- a/src/Build/Evaluation/Expander.cs +++ b/src/Build/Evaluation/Expander.cs @@ -28,6 +28,7 @@ using ParseArgs = Microsoft.Build.Evaluation.Expander.ArgumentParser; using TaskItem = Microsoft.Build.Execution.ProjectItemInstance.TaskItem; using TaskItemFactory = Microsoft.Build.Execution.ProjectItemInstance.TaskItem.TaskItemFactory; +using System.Buffers; #nullable disable @@ -279,17 +280,11 @@ private void FlushFirstValueIfNeeded() private static readonly char[] s_backtickChar = { '`' }; private static readonly char[] s_doubleQuoteChar = { '"' }; - /// - /// Those characters which indicate that an expression may contain expandable - /// expressions. - /// - private static char[] s_expandableChars = { '$', '%', '@' }; - /// /// The CultureInfo from the invariant culture. Used to avoid allocations for /// performing IndexOf etc. /// - private static CompareInfo s_invariantCompareInfo = CultureInfo.InvariantCulture.CompareInfo; + private static readonly CompareInfo s_invariantCompareInfo = CultureInfo.InvariantCulture.CompareInfo; /// /// Properties to draw on for expansion. @@ -454,7 +449,7 @@ internal PropertiesUseTracker PropertiesUseTracker /// internal static bool ExpressionMayContainExpandableExpressions(string expression) { - return expression.IndexOfAny(s_expandableChars) > -1; + return expression.AsSpan().IndexOfAny('$', '%', '@') >= 0; } /// @@ -1110,7 +1105,12 @@ _metadata is IItemTypeDefinition itemMetadata && if (IsTruncationEnabled(_options) && metadataValue.Length > CharacterLimitPerExpansion) { - metadataValue = metadataValue.Substring(0, CharacterLimitPerExpansion - 3) + "..."; + metadataValue = +#if NET + $"{metadataValue.AsSpan(0, CharacterLimitPerExpansion - 3)}..."; +#else + $"{metadataValue.Substring(0, CharacterLimitPerExpansion - 3)}..."; +#endif } } @@ -1313,7 +1313,12 @@ internal static object ExpandPropertiesLeaveTypedAndEscaped( var value = propertyValue.ToString(); if (value.Length > CharacterLimitPerExpansion) { - propertyValue = value.Substring(0, CharacterLimitPerExpansion - 3) + "..."; + propertyValue = +#if NET + $"{value.AsSpan(0, CharacterLimitPerExpansion - 3)}..."; +#else + $"{value.Substring(0, CharacterLimitPerExpansion - 3)}..."; +#endif } } @@ -1730,7 +1735,7 @@ private static string ExpandRegistryValue(string registryExpression, IElementLoc } catch (Exception ex) when (!ExceptionHandling.NotExpectedRegistryException(ex)) { - ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "InvalidRegistryPropertyExpression", "$(" + registryExpression + ")", ex.Message); + ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "InvalidRegistryPropertyExpression", $"$({registryExpression})", ex.Message); } } @@ -1858,7 +1863,7 @@ internal static ExpressionShredder.ItemExpressionCapture ExpandSingleItemVectorE } List matches; - if (expression.IndexOf('@') == -1) + if (!expression.Contains('@')) { return null; } @@ -2239,7 +2244,7 @@ internal static class IntrinsicItemFunctions /// /// A cache of previously created item function delegates. /// - private static ConcurrentDictionary s_transformFunctionDelegateCache = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + private static readonly ConcurrentDictionary s_transformFunctionDelegateCache = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); /// /// Delegate that represents the signature of all item transformation functions @@ -2254,7 +2259,7 @@ internal static class IntrinsicItemFunctions internal static ItemTransformFunction GetItemTransformFunction(IElementLocation elementLocation, string functionName, Type itemType) { ItemTransformFunction transformFunction = null; - string qualifiedFunctionName = itemType.FullName + "::" + functionName; + string qualifiedFunctionName = $"{itemType.FullName}::{functionName}"; // We may have seen this delegate before, if so grab the one we already created if (!s_transformFunctionDelegateCache.TryGetValue(qualifiedFunctionName, out transformFunction)) @@ -2631,7 +2636,7 @@ internal static IEnumerable> Metadata(Expander exp { // It may be that the itemspec has unescaped ';'s in it so we need to split here to handle // that case. - if (metadataValue.IndexOf(';') >= 0) + if (metadataValue.Contains(';')) { var splits = ExpressionShredder.SplitSemiColonSeparatedList(metadataValue); @@ -3095,36 +3100,23 @@ private static partial class RegularExpressions * description of an item vector changes, the expressions must be updated in both places. *************************************************************************************************************************/ - - -#if NET7_0_OR_GREATER +#if NET [GeneratedRegex(ItemMetadataSpecification, RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture)] - internal static partial Regex ItemMetadataPattern(); + internal static partial Regex ItemMetadataRegex { get; } #else /// /// Regular expression used to match item metadata references embedded in strings. /// For example, %(Compile.DependsOn) or %(DependsOn). /// - internal static readonly Lazy ItemMetadataPattern = new Lazy( - () => new Regex(ItemMetadataSpecification, - RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture | RegexOptions.Compiled)); -#endif + internal static Regex ItemMetadataRegex => s_itemMetadataRegex ??= + new Regex(ItemMetadataSpecification, RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture | RegexOptions.Compiled); - internal static Regex ItemMetadataRegex - { - get - { -#if NET7_0_OR_GREATER - return ItemMetadataPattern(); -#else - return ItemMetadataPattern.Value; + internal static Regex s_itemMetadataRegex; #endif - } - } - /// - /// Name of the group matching the "name" of a metadatum. - /// + /// + /// Name of the group matching the "name" of a metadatum. + /// internal const string NameGroup = "NAME"; /// @@ -3143,29 +3135,19 @@ internal static Regex ItemMetadataRegex ItemVectorWithTransformLHS + @")" + ItemMetadataSpecification + @"(?!" + ItemVectorWithTransformRHS + @"))"; -#if NET7_0_OR_GREATER +#if NET [GeneratedRegex(NonTransformItemMetadataSpecification, RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture)] - internal static partial Regex NonTransformItemMetadataPattern(); + internal static partial Regex NonTransformItemMetadataRegex { get; } #else /// /// regular expression used to match item metadata references outside of item vector transforms. /// /// PERF WARNING: this Regex is complex and tends to run slowly. - internal static readonly Lazy NonTransformItemMetadataPattern = new Lazy( - () => new Regex(NonTransformItemMetadataSpecification, - RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture | RegexOptions.Compiled)); -#endif - internal static Regex NonTransformItemMetadataRegex - { - get - { -#if NET7_0_OR_GREATER - return NonTransformItemMetadataPattern(); -#else - return NonTransformItemMetadataPattern.Value; + private static Regex s_nonTransformItemMetadataPattern; + + internal static Regex NonTransformItemMetadataRegex => s_nonTransformItemMetadataPattern ??= + new Regex(NonTransformItemMetadataSpecification, RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture | RegexOptions.Compiled); #endif - } - } /// /// Complete description of an item metadata reference, including the optional qualifying item type. @@ -4114,8 +4096,8 @@ private static object[] CoerceArguments(object[] args, ParameterInfo[] parameter else if (parameters[n].ParameterType.GetTypeInfo().IsEnum && args[n] is string v && v.Contains(".")) { Type enumType = parameters[n].ParameterType; - string typeLeafName = enumType.Name + "."; - string typeFullName = enumType.FullName + "."; + string typeLeafName = $"{enumType.Name}."; + string typeFullName = $"{enumType.FullName}."; // Enum.parse expects commas between enum components // We'll support the C# type | syntax too @@ -4200,11 +4182,11 @@ private string GenerateStringOfMethodExecuted(string expression, object objectIn } if ((_bindingFlags & BindingFlags.InvokeMethod) == BindingFlags.InvokeMethod) { - return "[" + typeName + "]::" + name + "(" + parameters + ")"; + return $"[{typeName}]::{name}({parameters})"; } else { - return "[" + typeName + "]::" + name; + return $"[{typeName}]::{name}"; } } else @@ -4213,11 +4195,11 @@ private string GenerateStringOfMethodExecuted(string expression, object objectIn if ((_bindingFlags & BindingFlags.InvokeMethod) == BindingFlags.InvokeMethod) { - return propertyValue + "." + name + "(" + parameters + ")"; + return $"{propertyValue}.{name}({parameters})"; } else { - return propertyValue + "." + name; + return $"{propertyValue}.{name}"; } } } diff --git a/src/Build/Evaluation/Expander/WellKnownFunctions.cs b/src/Build/Evaluation/Expander/WellKnownFunctions.cs index 72f8b247933..bc7a74e0a74 100644 --- a/src/Build/Evaluation/Expander/WellKnownFunctions.cs +++ b/src/Build/Evaluation/Expander/WellKnownFunctions.cs @@ -236,7 +236,7 @@ internal static bool TryExecuteStringFunction(string methodName, out object? ret { if (ParseArgs.TryGetArg(args, out string? arg0) && arg0 != null) { - returnVal = text.IndexOfAny(arg0.ToCharArray()); + returnVal = text.AsSpan().IndexOfAny(arg0.AsSpan()); return true; } } @@ -262,7 +262,7 @@ internal static bool TryExecuteStringFunction(string methodName, out object? ret { if (ParseArgs.TryGetArg(args, out string? arg0) && arg0 != null) { - returnVal = text.LastIndexOfAny(arg0.ToCharArray()); + returnVal = text.AsSpan().LastIndexOfAny(arg0.AsSpan()); return true; } } diff --git a/src/Build/Evaluation/ExpressionShredder.cs b/src/Build/Evaluation/ExpressionShredder.cs index fd102dff143..a7d9e48b9a5 100644 --- a/src/Build/Evaluation/ExpressionShredder.cs +++ b/src/Build/Evaluation/ExpressionShredder.cs @@ -428,7 +428,7 @@ private static void GetReferencedItemNamesAndMetadata(string expression, int sta itemName = firstPart; metadataName = expression.Substring(startOfText, i - startOfText); - qualifiedMetadataName = itemName + "." + metadataName; + qualifiedMetadataName = $"{itemName}.{metadataName}"; } else { diff --git a/src/Build/Evaluation/IntrinsicFunctions.cs b/src/Build/Evaluation/IntrinsicFunctions.cs index 032f95a251c..7d6b051cf99 100644 --- a/src/Build/Evaluation/IntrinsicFunctions.cs +++ b/src/Build/Evaluation/IntrinsicFunctions.cs @@ -22,6 +22,8 @@ // Needed for DoesTaskHostExistForParameters using NodeProviderOutOfProcTaskHost = Microsoft.Build.BackEnd.NodeProviderOutOfProcTaskHost; +using System.Security.Cryptography; +using System.Buffers.Text; #nullable disable @@ -40,25 +42,14 @@ internal static partial class IntrinsicFunctions private static readonly object[] DefaultRegistryViews = [RegistryView.Default]; #pragma warning restore CA1416 -#if NET7_0_OR_GREATER +#if NET [GeneratedRegex(RegistrySdkSpecification, RegexOptions.IgnoreCase)] - private static partial Regex RegistrySdkPattern(); + private static partial Regex RegistrySdkRegex { get; } #else - private static readonly Lazy RegistrySdkPattern = new Lazy(() => new Regex(RegistrySdkSpecification, RegexOptions.IgnoreCase)); + private static Regex s_registrySdkRegex; + private static Regex RegistrySdkRegex => s_registrySdkRegex ??= new Regex(RegistrySdkSpecification, RegexOptions.IgnoreCase); #endif - private static Regex RegistrySdkRegex - { - get - { -#if NET7_0_OR_GREATER - return RegistrySdkPattern(); -#else - return RegistrySdkPattern.Value; -#endif - } - } - private static readonly Lazy NuGetFramework = new Lazy(() => NuGetFrameworkWrapper.CreateInstance()); /// @@ -284,8 +275,8 @@ internal static object GetRegistryValueFromView(string keyName, string valueName { if (viewObject is string viewAsString) { - string typeLeafName = typeof(RegistryView).Name + "."; - string typeFullName = typeof(RegistryView).FullName + "."; + string typeLeafName = $"{typeof(RegistryView).Name}."; + string typeFullName = $"{typeof(RegistryView).FullName}."; // We'll allow the user to specify the leaf or full type name on the RegistryView enum viewAsString = viewAsString.Replace(typeFullName, "").Replace(typeLeafName, ""); @@ -466,7 +457,12 @@ internal static object StableStringHash(string toHash, StringHashingAlgorithm al private static string CalculateSha256(string toHash) { - using var sha = System.Security.Cryptography.SHA256.Create(); +#if NET + Span hash = stackalloc byte[SHA256.HashSizeInBytes]; + SHA256.HashData(Encoding.UTF8.GetBytes(toHash), hash); + return Convert.ToHexStringLower(hash); +#else + using var sha = SHA256.Create(); var hashResult = new StringBuilder(); foreach (byte theByte in sha.ComputeHash(Encoding.UTF8.GetBytes(toHash))) { @@ -474,6 +470,7 @@ private static string CalculateSha256(string toHash) } return hashResult.ToString(); +#endif } /// @@ -651,14 +648,15 @@ internal static string SubstringByAsciiChars(string input, int start, int length { return string.Empty; } + if (start + length > input.Length) { length = input.Length - start; } + StringBuilder sb = new StringBuilder(); - for (int i = start; i < start + length; i++) + foreach (char c in input.AsSpan(start, length)) { - char c = input[i]; if (c >= 32 && c <= 126 && !FileUtilities.InvalidFileNameChars.Contains(c)) { sb.Append(c); @@ -668,6 +666,7 @@ internal static string SubstringByAsciiChars(string input, int start, int length sb.Append('_'); } } + return sb.ToString(); } @@ -804,7 +803,7 @@ private static RegistryKey GetBaseKeyFromKeyName(string keyName, RegistryView vi } else { - subKeyName = keyName.Substring(i + 1, keyName.Length - i - 1); + subKeyName = keyName.Substring(i + 1); } return basekey; diff --git a/src/Build/Evaluation/LazyItemEvaluator.RemoveOperation.cs b/src/Build/Evaluation/LazyItemEvaluator.RemoveOperation.cs index 771b0e6ce40..51d91cce6d7 100644 --- a/src/Build/Evaluation/LazyItemEvaluator.RemoveOperation.cs +++ b/src/Build/Evaluation/LazyItemEvaluator.RemoveOperation.cs @@ -29,7 +29,7 @@ public RemoveOperation(RemoveOperationBuilder builder, LazyItemEvaluator(builder.MatchOnMetadataOptions, _matchOnMetadata, _itemSpec); } @@ -48,7 +48,7 @@ protected override void ApplyImpl(OrderedItemDataCollection.Builder listBuilder, return; } - bool matchingOnMetadata = _matchOnMetadata.Any(); + bool matchingOnMetadata = !_matchOnMetadata.IsEmpty; if (!matchingOnMetadata) { if (ItemspecContainsASingleBareItemReference(_itemSpec, _itemElement.ItemType)) diff --git a/src/Build/Evaluation/Preprocessor.cs b/src/Build/Evaluation/Preprocessor.cs index 0d38472e450..d70e8327285 100644 --- a/src/Build/Evaluation/Preprocessor.cs +++ b/src/Build/Evaluation/Preprocessor.cs @@ -26,6 +26,9 @@ namespace Microsoft.Build.Evaluation /// internal class Preprocessor { + /// 140 equal signs. + private const string Equals140 = "============================================================================================================================================"; + /// /// Project to preprocess /// @@ -99,7 +102,7 @@ private XmlDocument Preprocess() if (!String.IsNullOrEmpty(_project.FullPath)) // Ignore in-memory projects { - destinationDocument.AppendChild(destinationDocument.CreateComment("\r\n" + new String('=', 140) + "\r\n" + _project.FullPath.Replace("--", "__") + "\r\n" + new String('=', 140) + "\r\n")); + destinationDocument.AppendChild(destinationDocument.CreateComment($"\r\n{Equals140}\r\n{_project.FullPath.Replace("--", "__")}\r\n{Equals140}\r\n")); } CloneChildrenResolvingImports(outerDocument, destinationDocument); @@ -310,7 +313,7 @@ private void CloneChildrenResolvingImports(XmlNode source, XmlNode destination) } destination.AppendChild(destinationDocument.CreateComment( - $"\r\n{new String('=', 140)}\r\n{importTag}\r\n\r\n{resolved.FullPath.Replace("--", "__")}\r\n{new String('=', 140)}\r\n")); + $"\r\n{Equals140}\r\n{importTag}\r\n\r\n{resolved.FullPath.Replace("--", "__")}\r\n{Equals140}\r\n")); _filePaths.Push(resolved.FullPath); CloneChildrenResolvingImports(innerDocument, destination); @@ -318,11 +321,11 @@ private void CloneChildrenResolvingImports(XmlNode source, XmlNode destination) if (i < resolvedList.Count - 1) { - destination.AppendChild(destinationDocument.CreateComment("\r\n" + new String('=', 140) + "\r\n \r\n" + new String('=', 140) + "\r\n")); + destination.AppendChild(destinationDocument.CreateComment($"\r\n{Equals140}\r\n \r\n{Equals140}\r\n")); } else { - destination.AppendChild(destinationDocument.CreateComment("\r\n" + new String('=', 140) + "\r\n \r\n\r\n" + _filePaths.Peek()?.Replace("--", "__") + "\r\n" + new String('=', 140) + "\r\n")); + destination.AppendChild(destinationDocument.CreateComment($"\r\n{Equals140}\r\n \r\n\r\n{_filePaths.Peek()?.Replace("--", "__")}\r\n{Equals140}\r\n")); } } @@ -339,7 +342,7 @@ private void CloneChildrenResolvingImports(XmlNode source, XmlNode destination) CloneChildrenResolvingImports(child, destination); - destination.AppendChild(destinationDocument.CreateComment("")); + destination.AppendChild(destinationDocument.CreateComment($"")); continue; } diff --git a/src/Build/Evaluation/Profiler/EvaluationLocationMarkdownPrettyPrinter.cs b/src/Build/Evaluation/Profiler/EvaluationLocationMarkdownPrettyPrinter.cs index 065f756fede..99f4bfdf8c6 100644 --- a/src/Build/Evaluation/Profiler/EvaluationLocationMarkdownPrettyPrinter.cs +++ b/src/Build/Evaluation/Profiler/EvaluationLocationMarkdownPrettyPrinter.cs @@ -38,14 +38,19 @@ protected override string NormalizeExpression(string description, EvaluationLoca return null; } - text = text.Replace(Separator, "\\" + Separator); + text = text.Replace(Separator, $"\\{Separator}"); if (text.Length > 100) { - text = text.Remove(100) + "..."; + text = +#if NET + $"{text.AsSpan(0, 100)}..."; +#else + $"{text.Remove(100)}..."; +#endif } - return '`' + text + '`'; + return $"`{text}`"; } } } diff --git a/src/Build/Evaluation/Profiler/EvaluationLocationPrettyPrinterBase.cs b/src/Build/Evaluation/Profiler/EvaluationLocationPrettyPrinterBase.cs index a1e8dc7f30d..ea8876e874f 100644 --- a/src/Build/Evaluation/Profiler/EvaluationLocationPrettyPrinterBase.cs +++ b/src/Build/Evaluation/Profiler/EvaluationLocationPrettyPrinterBase.cs @@ -65,7 +65,7 @@ protected static string GetElementOrConditionText(string description, Evaluation var outerXml = description; outerXml = outerXml.Replace(@"xmlns=""http://schemas.microsoft.com/developer/msbuild/2003""", ""); - var newLineIndex = outerXml.IndexOfAny(['\r', '\n']); + var newLineIndex = outerXml.AsSpan().IndexOfAny('\r', '\n'); return newLineIndex == -1 ? outerXml : outerXml.Remove(newLineIndex); } @@ -74,9 +74,19 @@ protected static string GetElementOrConditionText(string description, Evaluation /// protected void AppendDefaultHeaderWithSeparator(StringBuilder stringBuilder, string separator) { - stringBuilder.AppendLine( - string.Join(separator, "Id", "ParentId", "Pass", "File", "Line #", "Expression", "Inc (ms)", "Inc (%)", "Exc (ms)", - "Exc (%)", "#", "Kind", "Bug")); + stringBuilder.Append("Id").Append(separator) + .Append("ParentId").Append(separator) + .Append("Pass").Append(separator) + .Append("File").Append(separator) + .Append("Line #").Append(separator) + .Append("Expression").Append(separator) + .Append("Inc (ms)").Append(separator) + .Append("Inc (%)").Append(separator) + .Append("Exc (ms)").Append(separator) + .Append("Exc (%)").Append(separator) + .Append('#').Append(separator) + .Append("Kind").Append(separator) + .Append("Bug").AppendLine(); } /// @@ -92,9 +102,9 @@ protected void AppendDefaultLocationWithSeparator(StringBuilder stringBuilder, T evaluationLocation.Line?.ToString() ?? string.Empty, NormalizeExpression(evaluationLocation.ElementDescription, evaluationLocation.Kind) ?? string.Empty, GetMilliseconds(profiledLocation.InclusiveTime), - GetPercentage(totalTime, profiledLocation.InclusiveTime) + "%", + $"{GetPercentage(totalTime, profiledLocation.InclusiveTime)}%", GetMilliseconds(profiledLocation.ExclusiveTime), - GetPercentage(totalTime, profiledLocation.ExclusiveTime) + "%", + $"{GetPercentage(totalTime, profiledLocation.ExclusiveTime)}%", profiledLocation.NumberOfHits, evaluationLocation.Kind + separator)); } diff --git a/src/Build/Evaluation/ProjectParser.cs b/src/Build/Evaluation/ProjectParser.cs index 3427a49260d..a71a572677d 100644 --- a/src/Build/Evaluation/ProjectParser.cs +++ b/src/Build/Evaluation/ProjectParser.cs @@ -577,7 +577,7 @@ private ProjectTargetElement ParseProjectTargetElement(XmlElementWithLocation el // Orcas compat: all target names are automatically unescaped string targetName = EscapingUtilities.UnescapeAll(ProjectXmlUtilities.GetAttributeValue(element, XMakeAttributes.name)); - int indexOfSpecialCharacter = targetName.IndexOfAny(XMakeElements.InvalidTargetNameCharacters); + int indexOfSpecialCharacter = targetName.AsSpan().IndexOfAny(XMakeElements.InvalidTargetNameCharacters); if (indexOfSpecialCharacter >= 0) { ProjectErrorUtilities.ThrowInvalidProject(element.GetAttributeLocation(XMakeAttributes.name), "NameInvalid", targetName, targetName[indexOfSpecialCharacter]); diff --git a/src/Build/Evaluation/ProjectRootElementCache.cs b/src/Build/Evaluation/ProjectRootElementCache.cs index d70e5648f3e..30e364ae5bd 100644 --- a/src/Build/Evaluation/ProjectRootElementCache.cs +++ b/src/Build/Evaluation/ProjectRootElementCache.cs @@ -79,7 +79,7 @@ internal class ProjectRootElementCache : ProjectRootElementCacheBase /// /// Whether the cache should log activity to the Debug.Out stream /// - private static bool s_debugLogCacheActivity = Environment.GetEnvironmentVariable("MSBUILDDEBUGXMLCACHE") == "1"; + private static readonly bool s_debugLogCacheActivity = Environment.GetEnvironmentVariable("MSBUILDDEBUGXMLCACHE") == "1"; /// /// Whether the cache should check file content for cache entry invalidation. @@ -87,7 +87,7 @@ internal class ProjectRootElementCache : ProjectRootElementCacheBase /// /// Value shall be true only in case of testing. Outside QA tests it shall be false. /// - private static bool s_сheckFileContent = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDCACHECHECKFILECONTENT")); + private static readonly bool s_сheckFileContent = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDCACHECHECKFILECONTENT")); #if DEBUG /// @@ -100,7 +100,7 @@ private struct ReentrancyGuard : IDisposable /// Shall be always 0 or 1. Reentrance to the Get function (value > 1) could lead to race condition. /// [ThreadStatic] - private static int s_getEntriesNumber = 0; + private static int s_getEntriesNumber; public ReentrancyGuard() { @@ -674,7 +674,7 @@ private void DebugTraceCache(string message, string param1) if (s_debugLogCacheActivity) { string prefix = OutOfProcNode.IsOutOfProcNode ? "C" : "P"; - Trace.WriteLine(prefix + " " + EnvironmentUtilities.CurrentProcessId + " | " + message + param1); + Trace.WriteLine($"{prefix} {Process.GetCurrentProcess().Id} | {message}{param1}"); } } } diff --git a/src/Build/Evaluation/StringMetadataTable.cs b/src/Build/Evaluation/StringMetadataTable.cs index 5e920a99c23..277c5d829d9 100644 --- a/src/Build/Evaluation/StringMetadataTable.cs +++ b/src/Build/Evaluation/StringMetadataTable.cs @@ -69,7 +69,7 @@ public string GetEscapedValueIfPresent(string itemType, string name) } else { - key = itemType + "." + name; + key = $"{itemType}.{name}"; } string value; diff --git a/src/Build/Globbing/MSBuildGlob.cs b/src/Build/Globbing/MSBuildGlob.cs index e6cb8ab1ac5..38915e44cd3 100644 --- a/src/Build/Globbing/MSBuildGlob.cs +++ b/src/Build/Globbing/MSBuildGlob.cs @@ -47,7 +47,7 @@ public GlobState(string globRoot, string fileSpec, bool isLegal, string fixedDir } // Cache of Regex objects that we have created and are still alive. - private static WeakValueDictionary s_regexCache = new WeakValueDictionary(); + private static readonly WeakValueDictionary s_regexCache = new WeakValueDictionary(); private readonly Lazy _state; diff --git a/src/Build/Graph/GraphBuilder.cs b/src/Build/Graph/GraphBuilder.cs index 311392df81f..09c7709e062 100644 --- a/src/Build/Graph/GraphBuilder.cs +++ b/src/Build/Graph/GraphBuilder.cs @@ -205,9 +205,9 @@ private static void AddEdgesFromSolution(IReadOnlyDictionary value)) { - projectsByPath[projectPath].Add(project.Value.GraphNode); + value.Add(project.Value.GraphNode); } else { diff --git a/src/Build/Graph/ParallelWorkSet.cs b/src/Build/Graph/ParallelWorkSet.cs index ed4ecb41537..8474968ed2c 100644 --- a/src/Build/Graph/ParallelWorkSet.cs +++ b/src/Build/Graph/ParallelWorkSet.cs @@ -145,7 +145,12 @@ internal void WaitForAllWorkAndComplete() // Release one thread that will release all the threads when all the elements are processed. _semaphore.Release(); - Task.WaitAll(_tasks.ToArray()); + Task.WaitAll( +#if NET + _tasks); +#else + _tasks.ToArray()); +#endif if (_exceptions.Count > 0) { diff --git a/src/Build/Graph/ProjectInterpretation.cs b/src/Build/Graph/ProjectInterpretation.cs index d927eaa7e8c..12ae373485d 100644 --- a/src/Build/Graph/ProjectInterpretation.cs +++ b/src/Build/Graph/ProjectInterpretation.cs @@ -64,7 +64,7 @@ public TargetSpecification(string target, bool skipIfNonexistent) ErrorUtilities.VerifyThrow( !skipIfNonexistent || (!target.Equals(MSBuildConstants.DefaultTargetsMarker) && !target.Equals(MSBuildConstants.ProjectReferenceTargetsOrDefaultTargetsMarker)), - target + " cannot be marked as SkipNonexistentTargets"); + $"{target} cannot be marked as SkipNonexistentTargets"); Target = target; SkipIfNonexistent = skipIfNonexistent; } diff --git a/src/Build/Instance/HostObjectException.cs b/src/Build/Instance/HostObjectException.cs index ee4613d1b14..47ba77e5d17 100644 --- a/src/Build/Instance/HostObjectException.cs +++ b/src/Build/Instance/HostObjectException.cs @@ -40,7 +40,7 @@ internal HostObjectException( Exception innerException) : base(ErrorMessagePrefix + string.Format(ErrorMessageProjectTargetTask, projectFile, targetName, taskName) - + (innerException == null ? string.Empty : ("\n=============\n" + innerException.ToString() + "\n\n")), + + (innerException == null ? string.Empty : ($"\n=============\n{innerException}\n\n")), innerException) { } diff --git a/src/Build/Instance/ProjectInstance.cs b/src/Build/Instance/ProjectInstance.cs index 938fcc5cba9..f77da95817b 100644 --- a/src/Build/Instance/ProjectInstance.cs +++ b/src/Build/Instance/ProjectInstance.cs @@ -2640,7 +2640,12 @@ private static ProjectInstance[] CalculateToolsVersionAndGenerateSolutionWrapper } else /* Dev 12 and above */ { - toolsVersion = visualStudioVersion.ToString(CultureInfo.InvariantCulture) + ".0"; + toolsVersion = +#if NET + string.Create(CultureInfo.InvariantCulture, $"{visualStudioVersion}.0"); +#else + $"{visualStudioVersion.ToString(CultureInfo.InvariantCulture)}.0"; +#endif } string toolsVersionToUse = Utilities.GenerateToolsVersionToUse( @@ -3161,7 +3166,7 @@ private void Initialize( if (Traits.Instance.EscapeHatches.DebugEvaluation) { - Trace.WriteLine(String.Format(CultureInfo.InvariantCulture, "MSBUILD: Creating a ProjectInstance from an unevaluated state [{0}]", FullPath)); + Trace.WriteLine($"MSBUILD: Creating a ProjectInstance from an unevaluated state [{FullPath}]"); } ErrorUtilities.VerifyThrow(EvaluationId == BuildEventContext.InvalidEvaluationId, "Evaluation ID is invalid prior to evaluation"); diff --git a/src/Build/Instance/ProjectItemInstance.cs b/src/Build/Instance/ProjectItemInstance.cs index 8dd5127eba5..36f280f88bb 100644 --- a/src/Build/Instance/ProjectItemInstance.cs +++ b/src/Build/Instance/ProjectItemInstance.cs @@ -2112,7 +2112,7 @@ internal class TaskItemFactory : IItemFactory, IItemFacto /// /// The singleton instance. /// - private static TaskItemFactory s_instance = new TaskItemFactory(); + private static readonly TaskItemFactory s_instance = new TaskItemFactory(); /// /// Private constructor for singleton creation. diff --git a/src/Build/Instance/ProjectMetadataInstance.cs b/src/Build/Instance/ProjectMetadataInstance.cs index ec764cbbbb3..15136b19c1d 100644 --- a/src/Build/Instance/ProjectMetadataInstance.cs +++ b/src/Build/Instance/ProjectMetadataInstance.cs @@ -164,7 +164,7 @@ internal string EvaluatedValueEscaped /// public override string ToString() { - return _name + "=" + _escapedValue; + return $"{_name}={_escapedValue}"; } #region INodePacketTranslatable Members diff --git a/src/Build/Instance/ProjectPropertyInstance.cs b/src/Build/Instance/ProjectPropertyInstance.cs index dc7cb2b6624..2d0de613ea9 100644 --- a/src/Build/Instance/ProjectPropertyInstance.cs +++ b/src/Build/Instance/ProjectPropertyInstance.cs @@ -186,7 +186,7 @@ void ITranslatable.Translate(ITranslator translator) /// public override string ToString() { - return _name + "=" + _escapedValue; + return $"{_name}={_escapedValue}"; } /// diff --git a/src/Build/Instance/TaskRegistry.cs b/src/Build/Instance/TaskRegistry.cs index a71dad69e15..12d084f9940 100644 --- a/src/Build/Instance/TaskRegistry.cs +++ b/src/Build/Instance/TaskRegistry.cs @@ -68,64 +68,64 @@ internal sealed class TaskRegistry : ITranslatable /// callbacks; as forcing those out of proc would be just setting them up for /// known failure. /// - private static bool s_forceTaskHostLaunch = (Environment.GetEnvironmentVariable("MSBUILDFORCEALLTASKSOUTOFPROC") == "1"); + private static readonly bool s_forceTaskHostLaunch = (Environment.GetEnvironmentVariable("MSBUILDFORCEALLTASKSOUTOFPROC") == "1"); /// /// Simple name for the MSBuild tasks (v4), used for shimming in loading /// task factory UsingTasks /// - private static string s_tasksV4SimpleName = "Microsoft.Build.Tasks.v4.0"; + private const string s_tasksV4SimpleName = "Microsoft.Build.Tasks.v4.0"; /// /// Filename for the MSBuild tasks (v4), used for shimming in loading /// task factory UsingTasks /// - private static string s_tasksV4Filename = s_tasksV4SimpleName + ".dll"; + private const string s_tasksV4Filename = $"{s_tasksV4SimpleName}.dll"; /// /// Expected location that MSBuild tasks (v4) is picked up from if the user /// references it with just a simple name, used for shimming in loading /// task factory UsingTasks /// - private static string s_potentialTasksV4Location = Path.Combine(BuildEnvironmentHelper.Instance.CurrentMSBuildToolsDirectory, s_tasksV4Filename); + private static readonly string s_potentialTasksV4Location = Path.Combine(BuildEnvironmentHelper.Instance.CurrentMSBuildToolsDirectory, s_tasksV4Filename); /// /// Simple name for the MSBuild tasks (v12), used for shimming in loading /// task factory UsingTasks /// - private static string s_tasksV12SimpleName = "Microsoft.Build.Tasks.v12.0"; + private const string s_tasksV12SimpleName = "Microsoft.Build.Tasks.v12.0"; /// /// Filename for the MSBuild tasks (v12), used for shimming in loading /// task factory UsingTasks /// - private static string s_tasksV12Filename = s_tasksV12SimpleName + ".dll"; + private const string s_tasksV12Filename = $"{s_tasksV12SimpleName}.dll"; /// /// Expected location that MSBuild tasks (v12) is picked up from if the user /// references it with just a simple name, used for shimming in loading /// task factory UsingTasks /// - private static string s_potentialTasksV12Location = Path.Combine(BuildEnvironmentHelper.Instance.CurrentMSBuildToolsDirectory, s_tasksV12Filename); + private static readonly string s_potentialTasksV12Location = Path.Combine(BuildEnvironmentHelper.Instance.CurrentMSBuildToolsDirectory, s_tasksV12Filename); /// /// Simple name for the MSBuild tasks (v14+), used for shimming in loading /// task factory UsingTasks /// - private static string s_tasksCoreSimpleName = "Microsoft.Build.Tasks.Core"; + private const string s_tasksCoreSimpleName = "Microsoft.Build.Tasks.Core"; /// /// Filename for the MSBuild tasks (v14+), used for shimming in loading /// task factory UsingTasks /// - private static string s_tasksCoreFilename = s_tasksCoreSimpleName + ".dll"; + private const string s_tasksCoreFilename = $"{s_tasksCoreSimpleName}.dll"; /// /// Expected location that MSBuild tasks (v14+) is picked up from if the user /// references it with just a simple name, used for shimming in loading /// task factory UsingTasks /// - private static string s_potentialTasksCoreLocation = Path.Combine(BuildEnvironmentHelper.Instance.CurrentMSBuildToolsDirectory, s_tasksCoreFilename); + private static readonly string s_potentialTasksCoreLocation = Path.Combine(BuildEnvironmentHelper.Instance.CurrentMSBuildToolsDirectory, s_tasksCoreFilename); /// /// Monotonically increasing counter for registered tasks. @@ -853,13 +853,13 @@ internal class RegisteredTaskIdentityComparer : IEqualityComparer /// The singleton comparer to use when an exact match is desired /// - private static RegisteredTaskIdentityComparer s_exact = new RegisteredTaskIdentityComparer(true /* exact match */); + private static readonly RegisteredTaskIdentityComparer s_exact = new RegisteredTaskIdentityComparer(true /* exact match */); /// /// The singleton comparer to use when a fuzzy match is desired. Note that this still does an exact match on the /// name, but does a fuzzy match on the task identity parameters. /// - private static RegisteredTaskIdentityComparer s_fuzzy = new RegisteredTaskIdentityComparer(false /* fuzzy match */); + private static readonly RegisteredTaskIdentityComparer s_fuzzy = new RegisteredTaskIdentityComparer(false /* fuzzy match */); /// /// Keeps track of whether we're doing exact or fuzzy equivalency diff --git a/src/Build/Logging/BaseConsoleLogger.cs b/src/Build/Logging/BaseConsoleLogger.cs index 508223e628b..364487c1e72 100644 --- a/src/Build/Logging/BaseConsoleLogger.cs +++ b/src/Build/Logging/BaseConsoleLogger.cs @@ -518,7 +518,7 @@ internal virtual void OutputEnvironment(IDictionary environment) foreach (KeyValuePair entry in environment) { setColor(ConsoleColor.Gray); - WritePretty(String.Format(CultureInfo.CurrentCulture, "{0,-30} = ", entry.Key)); + WritePretty($"{entry.Key,-30} = "); setColor(ConsoleColor.DarkGray); WriteLinePretty(entry.Value); } @@ -536,7 +536,7 @@ internal virtual void OutputProperties(List list) foreach (DictionaryEntry prop in list) { setColor(ConsoleColor.Gray); - WritePretty(String.Format(CultureInfo.CurrentCulture, "{0,-30} = ", prop.Key)); + WritePretty($"{prop.Key,-30} = "); setColor(ConsoleColor.DarkGray); WriteLinePretty(EscapingUtilities.UnescapeAll((string)prop.Value)); } @@ -656,12 +656,12 @@ protected virtual void WriteItemType(string itemType) protected virtual void WriteItemSpec(string itemSpec) { - WriteLinePretty(" " + itemSpec); + WriteLinePretty($" {itemSpec}"); } protected virtual void WriteMetadata(string name, string value) { - WriteLinePretty(" " + name + " = " + value); + WriteLinePretty($" {name} = {value}"); } /// @@ -821,8 +821,8 @@ internal virtual void PrintCounterMessage(WriteLinePrettyFromResourceDelegate wr 2, "PerformanceLine", time, - String.Format(CultureInfo.CurrentCulture, "{0,-40}" /* pad to 40 align left */, scopeName), - String.Format(CultureInfo.CurrentCulture, "{0,3}", calls)); + $"{scopeName,-40}", // pad to 40 align left + $"{calls,3}"); } /// diff --git a/src/Build/Logging/BinaryLogger/BuildEventArgsReader.cs b/src/Build/Logging/BinaryLogger/BuildEventArgsReader.cs index 4b48d9a8592..69afeee1674 100644 --- a/src/Build/Logging/BinaryLogger/BuildEventArgsReader.cs +++ b/src/Build/Logging/BinaryLogger/BuildEventArgsReader.cs @@ -56,9 +56,9 @@ public class BuildEventArgsReader : IBuildEventArgsReaderNotifications, IDisposa // reflection is needed to set these three fields because public constructors don't provide // a way to set these from the outside - private static FieldInfo? buildEventArgsFieldThreadId = + private static readonly FieldInfo? buildEventArgsFieldThreadId = typeof(BuildEventArgs).GetField("threadId", BindingFlags.Instance | BindingFlags.NonPublic); - private static FieldInfo? buildEventArgsFieldSenderName = + private static readonly FieldInfo? buildEventArgsFieldSenderName = typeof(BuildEventArgs).GetField("senderName", BindingFlags.Instance | BindingFlags.NonPublic); /// diff --git a/src/Build/Logging/BinaryLogger/Postprocessing/SubStream.cs b/src/Build/Logging/BinaryLogger/Postprocessing/SubStream.cs index 8c6e0c6e2b8..af92788d62a 100644 --- a/src/Build/Logging/BinaryLogger/Postprocessing/SubStream.cs +++ b/src/Build/Logging/BinaryLogger/Postprocessing/SubStream.cs @@ -70,9 +70,13 @@ public override int ReadByte() public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { count = Math.Min((int)Math.Max(Length - _position, 0), count); -#pragma warning disable CA1835 // Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' - int read = await _stream.ReadAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false); -#pragma warning restore CA1835 // Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' + int read = await _stream.ReadAsync( +#if NET + buffer.AsMemory(offset, count), +#else + buffer, offset, count, +#endif + cancellationToken).ConfigureAwait(false); _position += read; return read; } diff --git a/src/Build/Logging/BinaryLogger/Postprocessing/TransparentReadStream.cs b/src/Build/Logging/BinaryLogger/Postprocessing/TransparentReadStream.cs index ea3fcb3c9c7..bd427fbb3bc 100644 --- a/src/Build/Logging/BinaryLogger/Postprocessing/TransparentReadStream.cs +++ b/src/Build/Logging/BinaryLogger/Postprocessing/TransparentReadStream.cs @@ -116,9 +116,13 @@ public override async Task ReadAsync(byte[] buffer, int offset, int count, count = (int)(_maxAllowedPosition - _position); } -#pragma warning disable CA1835 // Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' - int cnt = await _stream.ReadAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false); -#pragma warning restore CA1835 // Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' + int cnt = await _stream.ReadAsync( +#if NET + buffer.AsMemory(offset, count), +#else + buffer, offset, count, +#endif + cancellationToken).ConfigureAwait(false); _position += cnt; return cnt; } diff --git a/src/Build/Logging/DistributedLoggers/DistributedFileLogger.cs b/src/Build/Logging/DistributedLoggers/DistributedFileLogger.cs index 591df6df9f3..bfd1d41a51f 100644 --- a/src/Build/Logging/DistributedLoggers/DistributedFileLogger.cs +++ b/src/Build/Logging/DistributedLoggers/DistributedFileLogger.cs @@ -113,7 +113,7 @@ public void Initialize(IEventSource eventSource) // but avoids confusion by being consistent with the Engine and any error messages it may produce. fileName = _logFile.Replace(extension, _nodeId + extension); _nodeFileLogger.Verbosity = LoggerVerbosity.Detailed; - _nodeFileLogger.Parameters = "ShowEventId;ShowCommandLine;logfile=" + fileName + ";" + _parameters; + _nodeFileLogger.Parameters = $"ShowEventId;ShowCommandLine;logfile={fileName};{_parameters}"; } catch (ArgumentException e) // Catching Exception, but rethrowing unless it's a well-known exception. { diff --git a/src/Build/Logging/LogFormatter.cs b/src/Build/Logging/LogFormatter.cs index b03391a34a1..22c0ab05658 100644 --- a/src/Build/Logging/LogFormatter.cs +++ b/src/Build/Logging/LogFormatter.cs @@ -51,7 +51,7 @@ internal static string FormatTimeSpan(TimeSpan t) string rawTime = t.ToString(); // Timespan is a value type and can't be null. int rawTimeLength = rawTime.Length; int prettyLength = System.Math.Min(11, rawTimeLength); - return t.ToString().Substring(0, prettyLength); + return rawTime.Substring(0, prettyLength); } } } diff --git a/src/Build/Logging/LoggerDescription.cs b/src/Build/Logging/LoggerDescription.cs index 8f949afb9ad..9b4e34e5c34 100644 --- a/src/Build/Logging/LoggerDescription.cs +++ b/src/Build/Logging/LoggerDescription.cs @@ -96,7 +96,7 @@ public string Name if (!string.IsNullOrEmpty(_loggerClassName) && !string.IsNullOrEmpty(_loggerAssembly.AssemblyFile)) { - return _loggerClassName + ":" + _loggerAssembly.AssemblyFile; + return $"{_loggerClassName}:{_loggerAssembly.AssemblyFile}"; } else if (!string.IsNullOrEmpty(_loggerClassName)) { diff --git a/src/Build/Logging/OptimizedStringIndenter.cs b/src/Build/Logging/OptimizedStringIndenter.cs index d98f1d62094..ed28c80e8fd 100644 --- a/src/Build/Logging/OptimizedStringIndenter.cs +++ b/src/Build/Logging/OptimizedStringIndenter.cs @@ -4,7 +4,7 @@ using System; using System.Buffers; -#if NET7_0_OR_GREATER +#if NET using System.Runtime.CompilerServices; #else using System.Text; @@ -49,7 +49,7 @@ namespace Microsoft.Build.BackEnd.Logging; internal static class OptimizedStringIndenter { #nullable enable -#if NET7_0_OR_GREATER +#if NET [SkipLocalsInit] #endif internal static unsafe string IndentString(string? s, int indent, IStringBuilderProvider stringBuilderProvider) @@ -67,7 +67,7 @@ internal static unsafe string IndentString(string? s, int indent, IStringBuilder indentedStringLength += segment.Length; } -#if NET7_0_OR_GREATER +#if NET #pragma warning disable CS8500 string result = string.Create(indentedStringLength, (s, (IntPtr)(&segments), indent), static (output, state) => { diff --git a/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs b/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs index d59bbb81e46..9e2368df650 100644 --- a/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs +++ b/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs @@ -760,7 +760,7 @@ internal override void OutputProperties(List list) foreach (DictionaryEntry prop in list) { setColor(ConsoleColor.Gray); - string propertyString = String.Format(CultureInfo.CurrentCulture, "{0} = {1}", prop.Key, EscapingUtilities.UnescapeAll((string)(prop.Value))); + string propertyString = $"{prop.Key} = {EscapingUtilities.UnescapeAll((string)(prop.Value))}"; WriteMessageAligned(propertyString, false); } resetColor(); @@ -781,7 +781,7 @@ internal override void OutputEnvironment(IDictionary environment foreach (KeyValuePair entry in environment) { setColor(ConsoleColor.Gray); - string environmentMessage = String.Format(CultureInfo.CurrentCulture, "{0} = {1}", entry.Key, entry.Value); + string environmentMessage = $"{entry.Key} = {entry.Value}"; WriteMessageAligned(environmentMessage, false); } } @@ -897,7 +897,7 @@ public override void TargetFinishedHandler(object sender, TargetFinishedEventArg foreach (DictionaryEntry metadatum in metadata) { - WriteMessageAligned(new String(' ', 4 * tabWidth) + metadatum.Key + " = " + item.GetMetadata(metadatum.Key as string), false); + WriteMessageAligned($"{new String(' ', 4 * tabWidth)}{metadatum.Key} = {item.GetMetadata(metadatum.Key as string)}", false); } } } @@ -1343,7 +1343,7 @@ private void PrintTargetNamePerMessage(BuildMessageEventArgs e, bool lightenText } else { - WriteMessageAligned(targetName + ":", prefixAlreadyWritten); + WriteMessageAligned($"{targetName}:", prefixAlreadyWritten); } if (lightenText) @@ -1618,11 +1618,11 @@ private void WriteLinePrefix(string key, DateTime eventTimeStamp, bool isMessage if (!isMessagePrefix || IsVerbosityAtLeast(LoggerVerbosity.Detailed)) { - prefixString = ResourceUtilities.FormatResourceStringStripCodeAndKeyword("BuildEventContext", context, key) + ">"; + prefixString = $"{ResourceUtilities.FormatResourceStringStripCodeAndKeyword("BuildEventContext", context, key)}>"; } else { - prefixString = ResourceUtilities.FormatResourceStringStripCodeAndKeyword("BuildEventContext", context, string.Empty) + " "; + prefixString = $"{ResourceUtilities.FormatResourceStringStripCodeAndKeyword("BuildEventContext", context, string.Empty)} "; } WritePretty(prefixString); @@ -1763,8 +1763,8 @@ internal override void PrintCounterMessage(WriteLinePrettyFromResourceDelegate W MessageIndentLevel, "PerformanceLine", time, - String.Format(CultureInfo.CurrentCulture, "{0,-40}" /* pad to 40 align left */, scopeName), - String.Format(CultureInfo.CurrentCulture, "{0,3}", calls)); + $"{scopeName,-40}", // pad to 40 align left + $"{calls,3}"); if (_internalPerformanceCounters?.Count > 0) { diff --git a/src/Build/Logging/ParallelLogger/ParallelLoggerHelpers.cs b/src/Build/Logging/ParallelLogger/ParallelLoggerHelpers.cs index dd73599bec2..b066b31fea4 100644 --- a/src/Build/Logging/ParallelLogger/ParallelLoggerHelpers.cs +++ b/src/Build/Logging/ParallelLogger/ParallelLoggerHelpers.cs @@ -140,7 +140,6 @@ internal string[] ProjectCallStackFromProject(BuildEventContext e) ProjectStartedEventMinimumFields startedEvent = GetProjectStartedEvent(currentKey); - List stackTrace = new List(); // If there is no started event then there should be no stack trace // this is a valid situation if the event occures in the engine or outside the context of a project // or the event is raised before the project started event @@ -150,19 +149,18 @@ internal string[] ProjectCallStackFromProject(BuildEventContext e) } List projectStackTrace = GetProjectCallStack(e); - foreach (ProjectStartedEventMinimumFields projectStartedEvent in projectStackTrace) + + string[] stackTrace = new string[projectStackTrace.Count]; + for (int i = 0; i < stackTrace.Length; i++) { - if (!string.IsNullOrEmpty(projectStartedEvent.TargetNames)) - { - stackTrace.Add(ResourceUtilities.FormatResourceStringStripCodeAndKeyword("ProjectStackWithTargetNames", projectStartedEvent.ProjectFile, projectStartedEvent.TargetNames, projectStartedEvent.FullProjectKey)); - } - else - { - stackTrace.Add(ResourceUtilities.FormatResourceStringStripCodeAndKeyword("ProjectStackWithDefaultTargets", projectStartedEvent.ProjectFile, projectStartedEvent.FullProjectKey)); - } + ProjectStartedEventMinimumFields projectStartedEvent = projectStackTrace[i]; + + stackTrace[stackTrace.Length - i - 1] = !string.IsNullOrEmpty(projectStartedEvent.TargetNames) ? + ResourceUtilities.FormatResourceStringStripCodeAndKeyword("ProjectStackWithTargetNames", projectStartedEvent.ProjectFile, projectStartedEvent.TargetNames, projectStartedEvent.FullProjectKey) : + ResourceUtilities.FormatResourceStringStripCodeAndKeyword("ProjectStackWithDefaultTargets", projectStartedEvent.ProjectFile, projectStartedEvent.FullProjectKey); } - stackTrace.Reverse(); - return stackTrace.ToArray(); + + return stackTrace; } /// diff --git a/src/Build/Logging/ProfilerLogger.cs b/src/Build/Logging/ProfilerLogger.cs index b80dcf8cf0e..85f659336d8 100644 --- a/src/Build/Logging/ProfilerLogger.cs +++ b/src/Build/Logging/ProfilerLogger.cs @@ -134,7 +134,7 @@ internal ProfilerResult GetAggregatedResult(bool pruneSmallItems = true) // So keeping that map here var originalLocations = new Dictionary(EvaluationLocationIdAgnosticComparer.Singleton); - while (_profiledResults.Any()) + while (!_profiledResults.IsEmpty) { ProfilerResult profiledResult; var result = _profiledResults.TryDequeue(out profiledResult); diff --git a/src/Build/Logging/TerminalLogger/TerminalLogger.cs b/src/Build/Logging/TerminalLogger/TerminalLogger.cs index 64f2cd341d6..44d5727c627 100644 --- a/src/Build/Logging/TerminalLogger/TerminalLogger.cs +++ b/src/Build/Logging/TerminalLogger/TerminalLogger.cs @@ -12,8 +12,10 @@ using Microsoft.Build.Framework.Logging; using Microsoft.Build.Shared; -#if NET7_0_OR_GREATER +#if NET using System.Diagnostics.CodeAnalysis; +using System.Buffers; + #endif #if NETFRAMEWORK @@ -34,15 +36,10 @@ public sealed partial class TerminalLogger : INodeLogger { private const string FilePathPattern = " -> "; -#if NET7_0_OR_GREATER - [StringSyntax(StringSyntaxAttribute.Regex)] - private const string ImmediateMessagePattern = @"\[CredentialProvider\]|--interactive"; - private const RegexOptions Options = RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture; - - [GeneratedRegex(ImmediateMessagePattern, Options)] - private static partial Regex ImmediateMessageRegex(); +#if NET + private static readonly SearchValues _immediateMessageKeywords = SearchValues.Create(["[CredentialProvider]", "--interactive"], StringComparison.OrdinalIgnoreCase); #else - private static readonly string[] _immediateMessageKeywords = { "[CredentialProvider]", "--interactive" }; + private static readonly string[] _immediateMessageKeywords = ["[CredentialProvider]", "--interactive"]; #endif private static readonly string[] newLineStrings = { "\r\n", "\n" }; @@ -165,11 +162,6 @@ public ProjectContext(BuildEventContext context) /// private bool _loggedPreviewMessage; - /// - /// The two directory separator characters to be passed to methods like . - /// - private static readonly char[] PathSeparators = { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }; - /// /// One summary per finished project test run. /// @@ -1018,8 +1010,8 @@ private void WarningRaised(object sender, BuildWarningEventArgs e) /// Raised event. /// true if marker is detected. private bool IsImmediateMessage(string message) => -#if NET7_0_OR_GREATER - ImmediateMessageRegex().IsMatch(message); +#if NET + message.AsSpan().ContainsAny(_immediateMessageKeywords); #else _immediateMessageKeywords.Any(imk => message.IndexOf(imk, StringComparison.OrdinalIgnoreCase) >= 0); #endif @@ -1195,7 +1187,7 @@ private int NodeIndexForContext(BuildEventContext context) return null; } - int index = path.LastIndexOfAny(PathSeparators); + int index = path.AsSpan().LastIndexOfAny(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); return index >= 0 ? $"{path.Substring(0, index + 1)}{AnsiCodes.MakeBold(path.Substring(index + 1))}" : path; @@ -1293,7 +1285,7 @@ private string FormatEventMessage( builder.Append($"{category} {code}: "); // render multi-line message in a special way - if (message.IndexOf('\n') >= 0) + if (message.Contains('\n')) { // Place the multiline message under the project in case of minimal and higher verbosity. string[] lines = message.Split(newLineStrings, StringSplitOptions.None); diff --git a/src/Build/Logging/TerminalLogger/TerminalNodeStatus.cs b/src/Build/Logging/TerminalLogger/TerminalNodeStatus.cs index 4634040bdf7..4661130b4c8 100644 --- a/src/Build/Logging/TerminalLogger/TerminalNodeStatus.cs +++ b/src/Build/Logging/TerminalLogger/TerminalNodeStatus.cs @@ -68,23 +68,10 @@ obj is TerminalNodeStatus status && TargetPrefixColor == status.TargetPrefixColor && TargetPrefix == status.TargetPrefix; - public override string ToString() - { - string duration = Stopwatch.ElapsedSeconds.ToString("F1"); - - return string.IsNullOrEmpty(TargetFramework) - ? string.Format("{0}{1} {2} ({3}s)", - TerminalLogger.Indentation, - Project, - Target, - duration) - : string.Format("{0}{1} {2} {3} ({4}s)", - TerminalLogger.Indentation, - Project, - AnsiCodes.Colorize(TargetFramework, TerminalLogger.TargetFrameworkColor), - Target, - duration); - } + public override string ToString() => + string.IsNullOrEmpty(TargetFramework) ? + $"{TerminalLogger.Indentation}{Project} {Target} ({Stopwatch.ElapsedSeconds:F1}s)" : + $"{TerminalLogger.Indentation}{Project} {AnsiCodes.Colorize(TargetFramework, TerminalLogger.TargetFrameworkColor)} {Target} ({Stopwatch.ElapsedSeconds:F1}s)"; public override int GetHashCode() { diff --git a/src/Build/Resources/Constants.cs b/src/Build/Resources/Constants.cs index f6c7a968081..68a6ab4f4bc 100644 --- a/src/Build/Resources/Constants.cs +++ b/src/Build/Resources/Constants.cs @@ -178,7 +178,7 @@ internal static class AvailableStaticMethods /// /// Locker to protect initialization /// - private static Object s_locker = new Object(); + private static readonly Object s_locker = new Object(); static AvailableStaticMethods() { @@ -365,7 +365,7 @@ private static void InitializeAvailableMethods() availableStaticMethods.TryAdd("Microsoft.Build.Utilities.ToolLocationHelper", new Tuple("Microsoft.Build.Utilities.ToolLocationHelper, Microsoft.Build.Utilities.Core, Version=" + MSBuildConstants.CurrentAssemblyVersion + ", Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", null)); availableStaticMethods.TryAdd("System.Runtime.InteropServices.RuntimeInformation", runtimeInformationType); availableStaticMethods.TryAdd("System.Runtime.InteropServices.OSPlatform", osPlatformType); -#if NET5_0_OR_GREATER +#if NET var operatingSystemType = new Tuple(null, typeof(OperatingSystem)); availableStaticMethods.TryAdd("System.OperatingSystem", operatingSystemType); #else diff --git a/src/Build/Utilities/SimpleVersion.cs b/src/Build/Utilities/SimpleVersion.cs index b12e865dfd1..ba17560035f 100644 --- a/src/Build/Utilities/SimpleVersion.cs +++ b/src/Build/Utilities/SimpleVersion.cs @@ -125,21 +125,19 @@ public static SimpleVersion Parse(string input) return new SimpleVersion(major, minor, build, revision); } - private static readonly char[] s_semverSeparators = ['-', '+']; - private static ReadOnlySpan RemoveTrivia(string input) { // Ignore leading/trailing whitespace in input. ReadOnlySpan span = input.AsSpan().Trim(); // Ignore a leading "v". - if (span.Length > 0 && (span[0] == 'v' || span[0] == 'V')) + if (span.Length > 0 && (span[0] is 'v' or 'V')) { span = span.Slice(1); } // Ignore semver separator and anything after. - int separatorIndex = span.IndexOfAny(s_semverSeparators); + int separatorIndex = span.IndexOfAny('-', '+'); if (separatorIndex >= 0) { span = span.Slice(0, separatorIndex); diff --git a/src/Build/Utilities/Utilities.cs b/src/Build/Utilities/Utilities.cs index 3e032c61c9c..79d23f8415b 100644 --- a/src/Build/Utilities/Utilities.cs +++ b/src/Build/Utilities/Utilities.cs @@ -2,11 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Buffers; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; +using System.Text; using System.Text.RegularExpressions; using System.Xml; using Microsoft.Build.BackEnd; @@ -26,7 +28,7 @@ namespace Microsoft.Build.Internal /// /// This class contains utility methods for the MSBuild engine. /// - internal static class Utilities + internal static partial class Utilities { /// /// Save off the contents of the environment variable that specifies whether we should treat higher toolsversions as the current @@ -81,7 +83,7 @@ internal static void SetXmlNodeInnerContents(XmlElementWithLocation node, string { ErrorUtilities.VerifyThrow(s != null, "Need value to set."); - if (s.IndexOf('<') != -1) + if (s.Contains('<')) { // If the value looks like it probably contains XML markup ... try @@ -295,7 +297,12 @@ private static bool ContainsNoTagsOtherThanComments(string innerXml, int firstLe } // used to find the xmlns attribute - private static readonly Regex s_xmlnsPattern = new Regex("xmlns=\"[^\"]*\"\\s*"); +#if NET + [GeneratedRegex("xmlns=\"[^\"]*\"\\s*")] + private static partial Regex XmlnsPattern { get; } +#else + private static Regex XmlnsPattern { get; } = new Regex("xmlns=\"[^\"]*\"\\s*"); +#endif /// /// Removes the xmlns attribute from an XML string. @@ -304,7 +311,7 @@ private static bool ContainsNoTagsOtherThanComments(string innerXml, int firstLe /// The modified XML string. internal static string RemoveXmlNamespace(string xml) { - return s_xmlnsPattern.Replace(xml, String.Empty); + return XmlnsPattern.Replace(xml, String.Empty); } /// @@ -312,19 +319,19 @@ internal static string RemoveXmlNamespace(string xml) /// internal static string CreateToolsVersionListString(IEnumerable toolsets) { - string toolsVersionList = String.Empty; + StringBuilder sb = StringBuilderCache.Acquire(); + foreach (Toolset toolset in toolsets) { - toolsVersionList += "\"" + toolset.ToolsVersion + "\", "; - } + if (sb.Length != 0) + { + sb.Append(", "); + } - // Remove trailing comma and space - if (toolsVersionList.Length > 0) - { - toolsVersionList = toolsVersionList.Substring(0, toolsVersionList.Length - 2); + sb.Append('"').Append(toolset.ToolsVersion).Append('"'); } - return toolsVersionList; + return StringBuilderCache.GetStringAndRelease(sb); } /// @@ -613,19 +620,6 @@ public static IEnumerable Values(this IEnumerable> } } - public static IEnumerable ToEnumerable(this IEnumerator enumerator) - { - while (enumerator.MoveNext()) - { - yield return enumerator.Current; - } - } - - public static T[] ToArray(this IEnumerator enumerator) - { - return enumerator.ToEnumerable().ToArray(); - } - /// /// Iterates through the nongeneric enumeration and provides generic strong-typed enumeration of properties. /// diff --git a/src/Framework/InternalErrorException.cs b/src/Framework/InternalErrorException.cs index e024e3f67a5..73180f14fb8 100644 --- a/src/Framework/InternalErrorException.cs +++ b/src/Framework/InternalErrorException.cs @@ -60,7 +60,7 @@ private InternalErrorException(string message, Exception innerException, bool ca ? message : "MSB0001: Internal MSBuild Error: " + message + (innerException == null ? String.Empty - : ("\n=============\n" + innerException.ToString() + "\n\n")), + : $"\n=============\n{innerException}\n\n"), innerException) { if (!calledFromDeserialization) diff --git a/src/Framework/LazyFormattedBuildEventArgs.cs b/src/Framework/LazyFormattedBuildEventArgs.cs index 209c30536d4..5cb00f9a8b2 100644 --- a/src/Framework/LazyFormattedBuildEventArgs.cs +++ b/src/Framework/LazyFormattedBuildEventArgs.cs @@ -193,7 +193,7 @@ private static string FormatString([StringSyntax(StringSyntaxAttribute.Composite // another one, add it here. if (param != null && param.ToString() == param.GetType().FullName) { - throw new InvalidOperationException(string.Format("Invalid type for message formatting argument, was {0}", param.GetType().FullName)); + throw new InvalidOperationException($"Invalid type for message formatting argument, was {param.GetType().FullName}"); } } #endif @@ -218,7 +218,7 @@ private static string FormatString([StringSyntax(StringSyntaxAttribute.Composite // Done executing task "Crash". // // T - formatted = string.Format("\"{0}\"\n{1}", unformatted, ex.ToString()); + formatted = $"\"{unformatted}\"\n{ex}"; } } diff --git a/src/Framework/NativeMethods.cs b/src/Framework/NativeMethods.cs index 48d91964772..ebd415b9e3c 100644 --- a/src/Framework/NativeMethods.cs +++ b/src/Framework/NativeMethods.cs @@ -461,7 +461,7 @@ public SystemInformationData() { ProcessorArchitectures processorArchitecture = ProcessorArchitectures.Unknown; -#if NETCOREAPP || NETSTANDARD1_1_OR_GREATER +#if NET || NETSTANDARD1_1_OR_GREATER // Get the architecture from the runtime. processorArchitecture = RuntimeInformation.OSArchitecture switch { @@ -469,13 +469,9 @@ public SystemInformationData() Architecture.Arm64 => ProcessorArchitectures.ARM64, Architecture.X64 => ProcessorArchitectures.X64, Architecture.X86 => ProcessorArchitectures.X86, -#if NET5_0_OR_GREATER +#if NET Architecture.Wasm => ProcessorArchitectures.WASM, -#endif -#if NET6_0_OR_GREATER Architecture.S390x => ProcessorArchitectures.S390X, -#endif -#if NET7_0_OR_GREATER Architecture.LoongArch64 => ProcessorArchitectures.LOONGARCH64, Architecture.Armv6 => ProcessorArchitectures.ARMV6, Architecture.Ppc64le => ProcessorArchitectures.PPC64LE, @@ -1394,7 +1390,7 @@ internal static int GetParentProcessId(int processId) // using (var r = FileUtilities.OpenRead("/proc/" + processId + "/stat")) // and could be again when FileUtilities moves to Framework - using var fileStream = new FileStream("/proc/" + processId + "/stat", FileMode.Open, System.IO.FileAccess.Read); + using var fileStream = new FileStream($"/proc/{processId}/stat", FileMode.Open, System.IO.FileAccess.Read); using StreamReader r = new(fileStream); line = r.ReadLine(); @@ -1571,7 +1567,7 @@ private static unsafe bool AreStringsEqual(char* buffer, int len, string s) return true; #else - return MemoryExtensions.SequenceEqual(new ReadOnlySpan(buffer, len), s.AsSpan()); + return s.AsSpan().SequenceEqual(new ReadOnlySpan(buffer, len)); #endif } diff --git a/src/Framework/OperatingSystem.cs b/src/Framework/OperatingSystem.cs index 883ec55b924..51756afe1da 100644 --- a/src/Framework/OperatingSystem.cs +++ b/src/Framework/OperatingSystem.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#if !NET5_0_OR_GREATER +#if !NET using System; diff --git a/src/MSBuild/AutomaticEncodingRestorer.cs b/src/MSBuild/AutomaticEncodingRestorer.cs index b5696d62ab8..034a0366786 100644 --- a/src/MSBuild/AutomaticEncodingRestorer.cs +++ b/src/MSBuild/AutomaticEncodingRestorer.cs @@ -24,7 +24,7 @@ public AutomaticEncodingRestorer() { try { -#if NET7_0_OR_GREATER +#if NET if (OperatingSystem.IsIOS() || OperatingSystem.IsAndroid() || OperatingSystem.IsTvOS()) // Output + Input Encoding are unavailable on these platforms per docs, and they're only available past net 5. { return; @@ -32,7 +32,7 @@ public AutomaticEncodingRestorer() #endif _originalOutputEncoding = Console.OutputEncoding; -#if NET7_0_OR_GREATER +#if NET if (OperatingSystem.IsBrowser()) // Input Encoding is also unavailable in this platform. (No concern for net472 as browser is unavailable.) { return; diff --git a/src/MSBuild/PerformanceLogEventListener.cs b/src/MSBuild/PerformanceLogEventListener.cs index 6772a6aeefc..2f0a0d31595 100644 --- a/src/MSBuild/PerformanceLogEventListener.cs +++ b/src/MSBuild/PerformanceLogEventListener.cs @@ -22,7 +22,7 @@ internal struct ProviderConfiguration internal EventLevel Level { get; set; } } - private static ProviderConfiguration[] s_config = + private static readonly ProviderConfiguration[] s_config = [ new ProviderConfiguration() { @@ -82,7 +82,7 @@ internal void Initialize(string logDirectory) _processIDStr = EnvironmentUtilities.CurrentProcessId.ToString(); // Use a GUID disambiguator to make sure that we have a unique file name. - string logFilePath = Path.Combine(logDirectory, $"perf-{_processIDStr}-{Guid.NewGuid().ToString("N")}.log"); + string logFilePath = Path.Combine(logDirectory, $"perf-{_processIDStr}-{Guid.NewGuid():N}.log"); Stream outputStream = new FileStream( logFilePath, @@ -143,7 +143,7 @@ protected override void OnEventWritten(EventWrittenEventArgs eventData) s_builder.Clear(); } - s_builder.Append($"[{DateTime.UtcNow.ToString("o")}] Event={eventData.EventSource.Name}/{eventData.EventName} ProcessID={_processIDStr} ThreadID={System.Threading.Thread.CurrentThread.ManagedThreadId}\t "); + s_builder.Append($"[{DateTime.UtcNow:o}] Event={eventData.EventSource.Name}/{eventData.EventName} ProcessID={_processIDStr} ThreadID={Environment.CurrentManagedThreadId}\t "); for (int i = 0; i < eventData.PayloadNames.Count; i++) { s_builder.Append($"{eventData.PayloadNames[i]}=\"{eventData.Payload[i]}\" "); diff --git a/src/MSBuild/XMake.cs b/src/MSBuild/XMake.cs index 8bf202edc41..357cb767e84 100644 --- a/src/MSBuild/XMake.cs +++ b/src/MSBuild/XMake.cs @@ -1777,7 +1777,7 @@ private static bool PrintTargets(string projectFile, string toolsVersion, Dictio ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword( "LongPaths", ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword( - "LongPaths_" + longPaths.ToString())), + $"LongPaths_{longPaths}")), MessageImportance.Low)); } @@ -1789,7 +1789,7 @@ private static bool PrintTargets(string projectFile, string toolsVersion, Dictio ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword( "SAC", ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword( - "SAC_" + SAC_State.ToString())), + $"SAC_{SAC_State}")), MessageImportance.Low)); } @@ -3094,7 +3094,7 @@ private static bool WarningsAsErrorsSwitchIsEmpty(CommandLineSwitches commandLin return false; } - int indexOfColon = val.IndexOf(":"); + int indexOfColon = val.IndexOf(':'); return indexOfColon < 0 || indexOfColon == val.Length - 1; } @@ -3663,13 +3663,13 @@ private static void ValidateExtensions(string[] projectExtensionsToIgnore) InitializationException.VerifyThrow(extension?.Length >= 2, "InvalidExtensionToIgnore", extension); // There is an invalid char in the extensionToIgnore. - InitializationException.VerifyThrow(extension.IndexOfAny(Path.GetInvalidPathChars()) == -1, "InvalidExtensionToIgnore", extension, null, false); + InitializationException.VerifyThrow(extension.AsSpan().IndexOfAny(MSBuildConstants.InvalidPathChars) < 0, "InvalidExtensionToIgnore", extension, null, false); // There were characters before the extension. InitializationException.VerifyThrow(string.Equals(extension, Path.GetExtension(extension), StringComparison.OrdinalIgnoreCase), "InvalidExtensionToIgnore", extension, null, false); // Make sure that no wild cards are in the string because for now we don't allow wild card extensions. - InitializationException.VerifyThrow(extension.IndexOfAny(s_wildcards) == -1, "InvalidExtensionToIgnore", extension, null, false); + InitializationException.VerifyThrow(extension.IndexOfAny(MSBuildConstants.WildcardChars) == -1, "InvalidExtensionToIgnore", extension, null, false); } } } @@ -3723,7 +3723,7 @@ private static string[] ProcessTargetSwitch(string[] parameters) { foreach (string parameter in parameters) { - int indexOfSpecialCharacter = parameter.IndexOfAny(XMakeElements.InvalidTargetNameCharacters); + int indexOfSpecialCharacter = parameter.AsSpan().IndexOfAny(XMakeElements.InvalidTargetNameCharacters); if (indexOfSpecialCharacter >= 0) { CommandLineSwitchException.Throw("NameInvalid", nameof(XMakeElements.target), parameter, parameter[indexOfSpecialCharacter].ToString()); @@ -3737,11 +3737,6 @@ private static string[] ProcessTargetSwitch(string[] parameters) /// private static readonly char[] s_propertyValueSeparator = MSBuildConstants.EqualsChar; - /// - /// This is a set of wildcard chars which can cause a file extension to be invalid - /// - private static readonly char[] s_wildcards = MSBuildConstants.WildcardChars; - /// /// Determines which ToolsVersion was specified on the command line. If more than /// one ToolsVersion was specified, we honor only the final ToolsVersion. diff --git a/src/Shared/AssemblyFolders/AssemblyFoldersEx.cs b/src/Shared/AssemblyFolders/AssemblyFoldersEx.cs index 6820a134454..a15f6e62113 100644 --- a/src/Shared/AssemblyFolders/AssemblyFoldersEx.cs +++ b/src/Shared/AssemblyFolders/AssemblyFoldersEx.cs @@ -82,7 +82,7 @@ internal AssemblyFoldersEx( return; } - bool is64bitOS = EnvironmentUtilities.Is64BitOperatingSystem; + bool is64bitOS = Environment.Is64BitOperatingSystem; bool targeting64bit = targetProcessorArchitecture == ProcessorArchitecture.Amd64 || targetProcessorArchitecture == ProcessorArchitecture.IA64; // The registry lookup should be as follows: @@ -367,7 +367,7 @@ internal static List GatherVersionStrings(string ta // Loop over versions from registry. foreach (string version in versions) { - if ((version.Length > 0) && (String.Equals(version.Substring(0, 1), "v", StringComparison.OrdinalIgnoreCase))) + if ((version.Length > 0) && version[0] is 'v' or 'V') { Version candidateVersion = VersionUtilities.ConvertToVersion(version); diff --git a/src/Shared/AssemblyFolders/AssemblyFoldersFromConfig.cs b/src/Shared/AssemblyFolders/AssemblyFoldersFromConfig.cs index b546b28ccb9..4f79794879d 100644 --- a/src/Shared/AssemblyFolders/AssemblyFoldersFromConfig.cs +++ b/src/Shared/AssemblyFolders/AssemblyFoldersFromConfig.cs @@ -41,7 +41,7 @@ internal AssemblyFoldersFromConfig(string configFile, string targetRuntimeVersio // Platform-agnostic folders first. FindDirectories(assemblyTargets, target => string.IsNullOrEmpty(target.Platform)); - if (EnvironmentUtilities.Is64BitOperatingSystem) + if (Environment.Is64BitOperatingSystem) { if (targeting64Bit) { diff --git a/src/Shared/AssemblyNameExtension.cs b/src/Shared/AssemblyNameExtension.cs index f29b9e8e443..12017934f1f 100644 --- a/src/Shared/AssemblyNameExtension.cs +++ b/src/Shared/AssemblyNameExtension.cs @@ -577,6 +577,11 @@ private static int CompareBaseNamesStringWise(string asString1, string asString2 baseLenThat = asString2.Length; } +#if NET + ReadOnlySpan nameThis = asString1.AsSpan(0, baseLenThis); + ReadOnlySpan nameThat = asString2.AsSpan(0, baseLenThat); + return nameThis.CompareTo(nameThat, StringComparison.OrdinalIgnoreCase); +#else // If the lengths are the same then we can compare without copying. if (baseLenThis == baseLenThat) { @@ -587,6 +592,7 @@ private static int CompareBaseNamesStringWise(string asString1, string asString2 string nameThis = asString1.Substring(0, baseLenThis); string nameThat = asString2.Substring(0, baseLenThat); return string.Compare(nameThis, nameThat, StringComparison.OrdinalIgnoreCase); +#endif } /// @@ -778,24 +784,18 @@ internal bool ComparePublicKeyToken(AssemblyNameExtension that) /// internal static bool ComparePublicKeyTokens(byte[] aPKT, byte[] bPKT) { +#if NET + return aPKT.AsSpan().SequenceEqual(bPKT.AsSpan()); +#else // Some assemblies (real case was interop assembly) may have null PKTs. - if (aPKT == null) - { -#pragma warning disable CA1825 // Avoid zero-length array allocations - aPKT = new byte[0]; -#pragma warning restore CA1825 // Avoid zero-length array allocations - } - if (bPKT == null) - { -#pragma warning disable CA1825 // Avoid zero-length array allocations - bPKT = new byte[0]; -#pragma warning restore CA1825 // Avoid zero-length array allocations - } + aPKT ??= []; + bPKT ??= []; if (aPKT.Length != bPKT.Length) { return false; } + for (int i = 0; i < aPKT.Length; ++i) { if (aPKT[i] != bPKT[i]) @@ -803,7 +803,9 @@ internal static bool ComparePublicKeyTokens(byte[] aPKT, byte[] bPKT) return false; } } + return true; +#endif } /// diff --git a/src/Shared/AwaitExtensions.cs b/src/Shared/AwaitExtensions.cs index 711d2dde947..f864c62bb23 100644 --- a/src/Shared/AwaitExtensions.cs +++ b/src/Shared/AwaitExtensions.cs @@ -19,7 +19,7 @@ internal static class AwaitExtensions /// /// Synchronizes access to the staScheduler field. /// - private static Object s_staSchedulerSync = new Object(); + private static readonly Object s_staSchedulerSync = new Object(); /// /// The singleton STA scheduler object. @@ -155,7 +155,7 @@ private class OneSTAThreadPerTaskScheduler : TaskScheduler /// /// The current queue of tasks. /// - private ConcurrentQueue _queuedTasks = new ConcurrentQueue(); + private readonly ConcurrentQueue _queuedTasks = new ConcurrentQueue(); /// /// Returns the list of queued tasks. diff --git a/src/Shared/CanonicalError.cs b/src/Shared/CanonicalError.cs index 011818c3f1c..4c4a8ea96cf 100644 --- a/src/Shared/CanonicalError.cs +++ b/src/Shared/CanonicalError.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Buffers; using System.Globalization; using System.Text.RegularExpressions; @@ -49,99 +50,141 @@ namespace Microsoft.Build.Shared /// /// <text> : warning [num]: <msg> /// - internal static class CanonicalError + internal static partial class CanonicalError { // Defines the main pattern for matching messages. - private static readonly Lazy s_originCategoryCodeTextExpression = new Lazy( - () => new Regex( - // Beginning of line and any amount of whitespace. - @"^\s*" - // Match a [optional project number prefix 'ddd>'], single letter + colon + remaining filename, or - // string with no colon followed by a colon. - + @"(((?(((\d+>)?[a-zA-Z]?:[^:]*)|([^:]*))):)" - // Origin may also be empty. In this case there's no trailing colon. - + "|())" - // Match the empty string or a string without a colon that ends with a space - + "(?(()|([^:]*? )))" - // Match 'error' or 'warning'. - + @"(?(error|warning))" - // Match anything starting with a space that's not a colon/space, followed by a colon. - // Error code is optional in which case "error"/"warning" can be followed immediately by a colon. - + @"( \s*(?[^: ]*))?\s*:" - // Whatever's left on this line, including colons. - + "(?.*)$", - RegexOptions.IgnoreCase | RegexOptions.Compiled)); - - private static readonly Lazy s_originCategoryCodeTextExpression2 = new Lazy( - () => new Regex( - @"^\s*(?(?.*):(?(?[0-9]*):(?[0-9]*))):(? error| warning):(?.*)", - RegexOptions.IgnoreCase | RegexOptions.Compiled)); + private const string OriginCategoryCodeTextExpressionPattern = + // Beginning of line and any amount of whitespace. + @"^\s*" + // Match a [optional project number prefix 'ddd>'], single letter + colon + remaining filename, or + // string with no colon followed by a colon. + + @"(((?(((\d+>)?[a-zA-Z]?:[^:]*)|([^:]*))):)" + // Origin may also be empty. In this case there's no trailing colon. + + "|())" + // Match the empty string or a string without a colon that ends with a space + + "(?(()|([^:]*? )))" + // Match 'error' or 'warning'. + + @"(?(error|warning))" + // Match anything starting with a space that's not a colon/space, followed by a colon. + // Error code is optional in which case "error"/"warning" can be followed immediately by a colon. + + @"( \s*(?[^: ]*))?\s*:" + // Whatever's left on this line, including colons. + + "(?.*)$"; + + private const string OriginCategoryCodeTextExpression2Pattern = + @"^\s*(?(?.*):(?(?[0-9]*):(?[0-9]*))):(? error| warning):(?.*)"; // Matches and extracts filename and location from an 'origin' element. - private static readonly Lazy s_filenameLocationFromOrigin = new Lazy( - () => new Regex( - "^" // Beginning of line - + @"(\d+>)?" // Optional ddd> project number prefix - + "(?.*)" // Match anything. - + @"\(" // Find a parenthesis. - + @"(?[\,,0-9,-]*)" // Match any combination of numbers and ',' and '-' - + @"\)\s*" // Find the closing paren then any amount of spaces. - + "$", // End-of-line - RegexOptions.IgnoreCase | RegexOptions.Compiled)); + private const string FilenameLocationFromOriginPattern = + "^" // Beginning of line + + @"(\d+>)?" // Optional ddd> project number prefix + + "(?.*)" // Match anything. + + @"\(" // Find a parenthesis. + + @"(?[\,,0-9,-]*)" // Match any combination of numbers and ',' and '-' + + @"\)\s*" // Find the closing paren then any amount of spaces. + + "$"; // End-of-line // Matches location that is a simple number. - private static readonly Lazy s_lineFromLocation = new Lazy( - () => new Regex( // Example: line - "^" // Beginning of line - + "(?[0-9]*)" // Match any number. - + "$", // End-of-line - RegexOptions.IgnoreCase | RegexOptions.Compiled)); + private const string LineFromLocationPattern = // Example: line + "^" // Beginning of line + + "(?[0-9]*)" // Match any number. + + "$"; // End-of-line // Matches location that is a range of lines. - private static readonly Lazy s_lineLineFromLocation = new Lazy( - () => new Regex( // Example: line-line - "^" // Beginning of line - + "(?[0-9]*)" // Match any number. - + "-" // Dash - + "(?[0-9]*)" // Match any number. - + "$", // End-of-line - RegexOptions.IgnoreCase | RegexOptions.Compiled)); + private const string LineLineFromLocationPattern = // Example: line-line + "^" // Beginning of line + + "(?[0-9]*)" // Match any number. + + "-" // Dash + + "(?[0-9]*)" // Match any number. + + "$"; // End-of-line // Matches location that is a line and column - private static readonly Lazy s_lineColFromLocation = new Lazy( - () => new Regex( // Example: line,col - "^" // Beginning of line - + "(?[0-9]*)" // Match any number. - + "," // Comma - + "(?[0-9]*)" // Match any number. - + "$", // End-of-line - RegexOptions.IgnoreCase | RegexOptions.Compiled)); + private const string LineColFromLocationPattern = // Example: line,col + "^" // Beginning of line + + "(?[0-9]*)" // Match any number. + + "," // Comma + + "(?[0-9]*)" // Match any number. + + "$"; // End-of-line // Matches location that is a line and column-range - private static readonly Lazy s_lineColColFromLocation = new Lazy( - () => new Regex( // Example: line,col-col - "^" // Beginning of line - + "(?[0-9]*)" // Match any number. - + "," // Comma - + "(?[0-9]*)" // Match any number. - + "-" // Dash - + "(?[0-9]*)" // Match any number. - + "$", // End-of-line - RegexOptions.IgnoreCase | RegexOptions.Compiled)); + private const string LineColColFromLocationPattern = // Example: line,col-col + "^" // Beginning of line + + "(?[0-9]*)" // Match any number. + + "," // Comma + + "(?[0-9]*)" // Match any number. + + "-" // Dash + + "(?[0-9]*)" // Match any number. + + "$"; // End-of-line // Matches location that is line,col,line,col - private static readonly Lazy s_lineColLineColFromLocation = new Lazy( - () => new Regex( // Example: line,col,line,col - "^" // Beginning of line - + "(?[0-9]*)" // Match any number. - + "," // Comma - + "(?[0-9]*)" // Match any number. - + "," // Dash - + "(?[0-9]*)" // Match any number. - + "," // Dash - + "(?[0-9]*)" // Match any number. - + "$", // End-of-line - RegexOptions.IgnoreCase | RegexOptions.Compiled)); + private const string LineColLineColFromLocationPattern = // Example: line,col,line,col + "^" // Beginning of line + + "(?[0-9]*)" // Match any number. + + "," // Comma + + "(?[0-9]*)" // Match any number. + + "," // Dash + + "(?[0-9]*)" // Match any number. + + "," // Dash + + "(?[0-9]*)" // Match any number. + + "$"; // End-of-line + +#if NET + [GeneratedRegex(OriginCategoryCodeTextExpressionPattern, RegexOptions.IgnoreCase)] + private static partial Regex OriginCategoryCodeTextExpression { get; } + + [GeneratedRegex(OriginCategoryCodeTextExpression2Pattern, RegexOptions.IgnoreCase)] + private static partial Regex OriginCategoryCodeTextExpression2 { get; } + + [GeneratedRegex(FilenameLocationFromOriginPattern, RegexOptions.IgnoreCase)] + private static partial Regex FilenameLocationFromOrigin { get; } + + [GeneratedRegex(LineFromLocationPattern, RegexOptions.IgnoreCase)] + private static partial Regex LineFromLocation { get; } + + [GeneratedRegex(LineLineFromLocationPattern, RegexOptions.IgnoreCase)] + private static partial Regex LineLineFromLocation { get; } + + [GeneratedRegex(LineColFromLocationPattern, RegexOptions.IgnoreCase)] + private static partial Regex LineColFromLocation { get; } + + [GeneratedRegex(LineColColFromLocationPattern, RegexOptions.IgnoreCase)] + private static partial Regex LineColColFromLocation { get; } + + [GeneratedRegex(LineColLineColFromLocationPattern, RegexOptions.IgnoreCase)] + private static partial Regex LineColLineColFromLocation { get; } +#else + private static Regex s_originCategoryCodeTextExpression; + private static Regex OriginCategoryCodeTextExpression => s_originCategoryCodeTextExpression ??= + new Regex(OriginCategoryCodeTextExpressionPattern, RegexOptions.IgnoreCase | RegexOptions.Compiled); + + private static Regex s_originCategoryCodeTextExpression2; + private static Regex OriginCategoryCodeTextExpression2 => s_originCategoryCodeTextExpression2 ??= + new Regex(OriginCategoryCodeTextExpression2Pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled); + + private static Regex s_filenameLocationFromOrigin; + private static Regex FilenameLocationFromOrigin => s_filenameLocationFromOrigin ??= + new Regex(FilenameLocationFromOriginPattern, RegexOptions.IgnoreCase | RegexOptions.Compiled); + + private static Regex s_lineFromLocation; + private static Regex LineFromLocation => s_lineFromLocation ??= + new Regex(LineFromLocationPattern, RegexOptions.IgnoreCase | RegexOptions.Compiled); + + private static Regex s_lineLineFromLocation; + private static Regex LineLineFromLocation => s_lineLineFromLocation ??= + new Regex(LineLineFromLocationPattern, RegexOptions.IgnoreCase | RegexOptions.Compiled); + + private static Regex s_lineColFromLocation; + private static Regex LineColFromLocation => s_lineColFromLocation ??= + new Regex(LineColFromLocationPattern, RegexOptions.IgnoreCase | RegexOptions.Compiled); + + private static Regex s_lineColColFromLocation; + private static Regex LineColColFromLocation => s_lineColColFromLocation ??= + new Regex(LineColColFromLocationPattern, RegexOptions.IgnoreCase | RegexOptions.Compiled); + + private static Regex s_lineColLineColFromLocation; + private static Regex LineColLineColFromLocation => s_lineColLineColFromLocation ??= + new Regex(LineColLineColFromLocationPattern, RegexOptions.IgnoreCase | RegexOptions.Compiled); +#endif /// /// Represents the parts of a decomposed canonical message. @@ -234,6 +277,10 @@ private static int ConvertToIntWithDefault(string value) return result; } +#if NET + private static readonly SearchValues s_warningOrError = SearchValues.Create(["warning", "error"], StringComparison.OrdinalIgnoreCase); +#endif + /// /// Decompose an error or warning message into constituent parts. If the message isn't in the canonical form, return null. /// @@ -256,8 +303,12 @@ internal static Parts Parse(string message) // If a tool has a large amount of output that isn't an error or warning (eg., "dir /s %hugetree%") // the regex below is slow. It's faster to pre-scan for "warning" and "error" // and bail out if neither are present. - if (message.IndexOf("warning", StringComparison.OrdinalIgnoreCase) == -1 && - message.IndexOf("error", StringComparison.OrdinalIgnoreCase) == -1) +#if NET + if (message.AsSpan().IndexOfAny(s_warningOrError) < 0) +#else + if (message.IndexOf("warning", StringComparison.OrdinalIgnoreCase) < 0 && + message.IndexOf("error", StringComparison.OrdinalIgnoreCase) < 0) +#endif { return null; } @@ -283,7 +334,7 @@ internal static Parts Parse(string message) // Here's an example from the Japanese version of LINK.EXE: // AssemblyInfo.cpp : fatal error LNK1106: ???????????? ??????????????: 0x6580 ?????????? // - Match match = s_originCategoryCodeTextExpression.Value.Match(message); + Match match = OriginCategoryCodeTextExpression.Match(message); string category; if (!match.Success) { @@ -292,7 +343,7 @@ internal static Parts Parse(string message) // err.cpp:6:3: error: use of undeclared identifier 'force_an_error' // ----------- ----- --------------------------------------------- // Origin Cat. Text - match = s_originCategoryCodeTextExpression2.Value.Match(message); + match = OriginCategoryCodeTextExpression2.Match(message); if (!match.Success) { return null; @@ -320,7 +371,7 @@ internal static Parts Parse(string message) string[] explodedText = parsedMessage.text.Split(MSBuildConstants.SingleQuoteChar, StringSplitOptions.RemoveEmptyEntries); if (explodedText.Length > 0) { - parsedMessage.code = "G" + explodedText[0].GetHashCode().ToString("X8"); + parsedMessage.code = $"G{explodedText[0].GetHashCode():X8}"; } else { @@ -353,7 +404,7 @@ internal static Parts Parse(string message) // Origin is not a simple file, but it still could be of the form, // foo.cpp(location) - match = s_filenameLocationFromOrigin.Value.Match(origin); + match = FilenameLocationFromOrigin.Match(origin); if (match.Success) { @@ -373,14 +424,14 @@ internal static Parts Parse(string message) // (line,col,line,col) if (location.Length > 0) { - match = s_lineFromLocation.Value.Match(location); + match = LineFromLocation.Match(location); if (match.Success) { parsedMessage.line = ConvertToIntWithDefault(match.Groups["LINE"].Value.Trim()); } else { - match = s_lineLineFromLocation.Value.Match(location); + match = LineLineFromLocation.Match(location); if (match.Success) { parsedMessage.line = ConvertToIntWithDefault(match.Groups["LINE"].Value.Trim()); @@ -388,7 +439,7 @@ internal static Parts Parse(string message) } else { - match = s_lineColFromLocation.Value.Match(location); + match = LineColFromLocation.Match(location); if (match.Success) { parsedMessage.line = ConvertToIntWithDefault(match.Groups["LINE"].Value.Trim()); @@ -396,7 +447,7 @@ internal static Parts Parse(string message) } else { - match = s_lineColColFromLocation.Value.Match(location); + match = LineColColFromLocation.Match(location); if (match.Success) { parsedMessage.line = ConvertToIntWithDefault(match.Groups["LINE"].Value.Trim()); @@ -405,7 +456,7 @@ internal static Parts Parse(string message) } else { - match = s_lineColLineColFromLocation.Value.Match(location); + match = LineColLineColFromLocation.Match(location); if (match.Success) { parsedMessage.line = ConvertToIntWithDefault(match.Groups["LINE"].Value.Trim()); diff --git a/src/Shared/CommunicationsUtilities.cs b/src/Shared/CommunicationsUtilities.cs index 12061206c4d..cb1177fce58 100644 --- a/src/Shared/CommunicationsUtilities.cs +++ b/src/Shared/CommunicationsUtilities.cs @@ -99,10 +99,10 @@ protected internal Handshake(HandshakeOptions nodeType) CommunicationsUtilities.Trace("Building handshake for node type {0}, (version {1}): options {2}.", nodeType, handshakeVersion, options); string handshakeSalt = Environment.GetEnvironmentVariable("MSBUILDNODEHANDSHAKESALT"); - CommunicationsUtilities.Trace("Handshake salt is " + handshakeSalt); + CommunicationsUtilities.Trace("Handshake salt is {0}", handshakeSalt); string toolsDirectory = BuildEnvironmentHelper.Instance.MSBuildToolsDirectoryRoot; - CommunicationsUtilities.Trace("Tools directory root is " + toolsDirectory); - salt = CommunicationsUtilities.GetHashCode(handshakeSalt + toolsDirectory); + CommunicationsUtilities.Trace("Tools directory root is {0}", toolsDirectory); + salt = CommunicationsUtilities.GetHashCode($"{handshakeSalt}{toolsDirectory}"); Version fileVersion = new Version(FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion); fileVersionMajor = fileVersion.Major; fileVersionMinor = fileVersion.Minor; @@ -115,7 +115,7 @@ protected internal Handshake(HandshakeOptions nodeType) // This is used as a key, so it does not need to be human readable. public override string ToString() { - return String.Format("{0} {1} {2} {3} {4} {5} {6}", options, salt, fileVersionMajor, fileVersionMinor, fileVersionBuild, fileVersionPrivate, sessionId); + return $"{options} {salt} {fileVersionMajor} {fileVersionMinor} {fileVersionBuild} {fileVersionPrivate} {sessionId}"; } public virtual int[] RetrieveHandshakeComponents() @@ -178,8 +178,14 @@ public string ComputeHash() if (_computedHash == null) { var input = GetKey(); + byte[] utf8 = Encoding.UTF8.GetBytes(input); +#if NET + Span bytes = stackalloc byte[SHA256.HashSizeInBytes]; + SHA256.HashData(utf8, bytes); +#else using var sha = SHA256.Create(); - var bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(input)); + var bytes = sha.ComputeHash(utf8); +#endif _computedHash = Convert.ToBase64String(bytes) .Replace("/", "_") .Replace("=", string.Empty); @@ -211,7 +217,7 @@ internal static class CommunicationsUtilities /// /// Whether to trace communications /// - private static bool s_trace = Traits.Instance.DebugNodeCommunication; + private static readonly bool s_trace = Traits.Instance.DebugNodeCommunication; /// /// Lock trace to ensure we are logging in serial fashion. @@ -581,7 +587,7 @@ internal static int ReadIntForHandshake(this PipeStream stream, byte? byteToAcce #nullable disable #if !FEATURE_APM - internal static async Task ReadAsync(Stream stream, byte[] buffer, int bytesToRead) + internal static async ValueTask ReadAsync(Stream stream, byte[] buffer, int bytesToRead) { int totalBytesRead = 0; while (totalBytesRead < bytesToRead) diff --git a/src/Shared/Constants.cs b/src/Shared/Constants.cs index e435d354935..4aa800ef2d2 100644 --- a/src/Shared/Constants.cs +++ b/src/Shared/Constants.cs @@ -2,6 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +#if NET +using System.Buffers; +#endif using System.IO; #nullable disable @@ -112,25 +115,31 @@ internal static class MSBuildConstants internal const string ProjectReferenceTargetsOrDefaultTargetsMarker = ".projectReferenceTargetsOrDefaultTargets"; // One-time allocations to avoid implicit allocations for Split(), Trim(). - internal static readonly char[] SemicolonChar = { ';' }; - internal static readonly char[] SpaceChar = { ' ' }; - internal static readonly char[] SingleQuoteChar = { '\'' }; - internal static readonly char[] EqualsChar = { '=' }; - internal static readonly char[] ColonChar = { ':' }; - internal static readonly char[] BackslashChar = { '\\' }; - internal static readonly char[] NewlineChar = { '\n' }; - internal static readonly char[] CrLf = { '\r', '\n' }; - internal static readonly char[] ForwardSlash = { '/' }; - internal static readonly char[] ForwardSlashBackslash = { '/', '\\' }; - internal static readonly char[] WildcardChars = { '*', '?' }; - internal static readonly string[] CharactersForExpansion = { "*", "?", "$(", "@(", "%" }; - internal static readonly char[] CommaChar = { ',' }; - internal static readonly char[] HyphenChar = { '-' }; - internal static readonly char[] DirectorySeparatorChar = { Path.DirectorySeparatorChar }; - internal static readonly char[] DotChar = { '.' }; - internal static readonly string[] EnvironmentNewLine = { Environment.NewLine }; - internal static readonly char[] PipeChar = { '|' }; - internal static readonly char[] PathSeparatorChar = { Path.PathSeparator }; + internal static readonly char[] SemicolonChar = [';']; + internal static readonly char[] SpaceChar = [' ']; + internal static readonly char[] SingleQuoteChar = ['\'']; + internal static readonly char[] EqualsChar = ['=']; + internal static readonly char[] ColonChar = [':']; + internal static readonly char[] BackslashChar = ['\\']; + internal static readonly char[] NewlineChar = ['\n']; + internal static readonly char[] CrLf = ['\r', '\n']; + internal static readonly char[] ForwardSlash = ['/']; + internal static readonly char[] ForwardSlashBackslash = ['/', '\\']; + internal static readonly char[] WildcardChars = ['*', '?']; + internal static readonly string[] CharactersForExpansion = ["*", "?", "$(", "@(", "%"]; + internal static readonly char[] CommaChar = [',']; + internal static readonly char[] HyphenChar = ['-']; + internal static readonly char[] DirectorySeparatorChar = [Path.DirectorySeparatorChar]; + internal static readonly char[] DotChar = ['.']; + internal static readonly string[] EnvironmentNewLine = [Environment.NewLine]; + internal static readonly char[] PipeChar = ['|']; + internal static readonly char[] PathSeparatorChar = [Path.PathSeparator]; + +#if NET + internal static readonly SearchValues InvalidPathChars = SearchValues.Create(Path.GetInvalidPathChars()); +#else + internal static readonly char[] InvalidPathChars = Path.GetInvalidPathChars(); +#endif } internal static class PropertyNames diff --git a/src/Shared/ConversionUtilities.cs b/src/Shared/ConversionUtilities.cs index 10bdc82790e..9f1e756baeb 100644 --- a/src/Shared/ConversionUtilities.cs +++ b/src/Shared/ConversionUtilities.cs @@ -57,6 +57,9 @@ internal static bool ConvertStringToBool(string parameterValue, bool nullOrWhite /// A string byte types formated as X2. internal static string ConvertByteArrayToHex(byte[] bytes) { +#if NET + return Convert.ToHexString(bytes); +#else var sb = new StringBuilder(); foreach (var b in bytes) { @@ -64,6 +67,7 @@ internal static string ConvertByteArrayToHex(byte[] bytes) } return sb.ToString(); +#endif } internal static bool TryConvertStringToBool(string parameterValue, out bool boolValue) @@ -131,7 +135,13 @@ internal static double ConvertDecimalToDouble(string number) /// internal static double ConvertHexToDouble(string number) { - return (double)Int32.Parse(number.Substring(2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture.NumberFormat); + return (double)Int32.Parse( +#if NET + number.AsSpan(2), +#else + number.Substring(2), +#endif + NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture.NumberFormat); } /// @@ -172,9 +182,15 @@ private static bool ValidHexNumber(string number, out int value) { bool canConvert = false; value = 0; - if (number.Length >= 3 && number[0] == '0' && (number[1] == 'x' || number[1] == 'X')) + if (number.Length >= 3 && number[0] is '0' && number[1] is 'x' or 'X') { - canConvert = Int32.TryParse(number.Substring(2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture.NumberFormat, out value); + canConvert = Int32.TryParse( +#if NET + number.AsSpan(2), +#else + number.Substring(2), +#endif + NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture.NumberFormat, out value); } return canConvert; } diff --git a/src/Shared/Debugging/PrintLineDebuggerWriters.cs b/src/Shared/Debugging/PrintLineDebuggerWriters.cs index e0dc425c320..bcdadc22a24 100644 --- a/src/Shared/Debugging/PrintLineDebuggerWriters.cs +++ b/src/Shared/Debugging/PrintLineDebuggerWriters.cs @@ -68,7 +68,7 @@ public CompositeWriter(IEnumerable writers) public static CommonWriterType StdOutWriter = (id, callsite, args) => Console.WriteLine(SimpleFormat(id, callsite, args)); - private static Lazy _artifactsLogs = new Lazy( + private static readonly Lazy _artifactsLogs = new Lazy( () => { var executingAssembly = FileUtilities.ExecutingAssemblyPath; diff --git a/src/Shared/EscapingUtilities.cs b/src/Shared/EscapingUtilities.cs index f84ddf86632..8bde0027840 100644 --- a/src/Shared/EscapingUtilities.cs +++ b/src/Shared/EscapingUtilities.cs @@ -26,7 +26,7 @@ internal static class EscapingUtilities /// Optional cache of escaped strings for use when needing to escape in performance-critical scenarios with significant /// expected string reuse. /// - private static Dictionary s_unescapedToEscapedStrings = new Dictionary(StringComparer.Ordinal); + private static readonly Dictionary s_unescapedToEscapedStrings = new Dictionary(StringComparer.Ordinal); private static bool TryDecodeHexDigit(char character, out int value) { diff --git a/src/Shared/FileMatcher.cs b/src/Shared/FileMatcher.cs index d6cd177e8ad..9d97c12de8d 100644 --- a/src/Shared/FileMatcher.cs +++ b/src/Shared/FileMatcher.cs @@ -26,14 +26,19 @@ internal class FileMatcher private readonly IFileSystem _fileSystem; private const string recursiveDirectoryMatch = "**"; - private static readonly string s_directorySeparator = new string(Path.DirectorySeparatorChar, 1); + private static readonly string s_directorySeparatorString = Path.DirectorySeparatorChar.ToString(); + private static readonly string s_twoDirectorySeparators = s_directorySeparatorString + s_directorySeparatorString; - private static readonly string s_thisDirectory = "." + s_directorySeparator; + private static readonly string s_thisDirectory = $".{s_directorySeparatorString}"; private static readonly char[] s_wildcardCharacters = { '*', '?' }; private static readonly char[] s_wildcardAndSemicolonCharacters = { '*', '?', ';' }; - private static readonly string[] s_propertyAndItemReferences = { "$(", "@(" }; +#if NET + private static readonly SearchValues s_propertyAndItemReferences = SearchValues.Create(["$(", "@("], StringComparison.Ordinal); +#else + private static readonly string[] s_propertyAndItemReferences = ["$(", "@("]; +#endif // on OSX both System.IO.Path separators are '/', so we have to use the literals internal static readonly char[] directorySeparatorCharacters = FileUtilities.Slashes; @@ -45,12 +50,6 @@ internal class FileMatcher private readonly ConcurrentDictionary> _cachedGlobExpansions; private readonly Lazy> _cachedGlobExpansionsLock = new Lazy>(() => new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase)); - /// - /// Cache of the list of invalid path characters, because this method returns a clone (for security reasons) - /// which can cause significant transient allocations - /// - private static readonly char[] s_invalidPathChars = Path.GetInvalidPathChars(); - public const RegexOptions DefaultRegexOptions = RegexOptions.IgnoreCase; private readonly GetFileSystemEntries _getFileSystemEntries; @@ -186,7 +185,7 @@ internal static bool HasWildcards(string filespec) // Choose LastIndexOfAny instead of IndexOfAny because it seems more likely // that wildcards will tend to be towards the right side. - return -1 != filespec.LastIndexOfAny(s_wildcardCharacters); + return filespec.LastIndexOfAny(s_wildcardCharacters) >= 0; } /// @@ -195,10 +194,8 @@ internal static bool HasWildcards(string filespec) internal static bool HasWildcardsSemicolonItemOrPropertyReferences(string filespec) { return - - (-1 != filespec.IndexOfAny(s_wildcardAndSemicolonCharacters)) || - HasPropertyOrItemReferences(filespec) - ; + (filespec.IndexOfAny(s_wildcardAndSemicolonCharacters) >= 0) || + HasPropertyOrItemReferences(filespec); } /// @@ -206,7 +203,12 @@ internal static bool HasWildcardsSemicolonItemOrPropertyReferences(string filesp /// internal static bool HasPropertyOrItemReferences(string filespec) { - return s_propertyAndItemReferences.Any(filespec.Contains); + return +#if NET + filespec.AsSpan().ContainsAny(s_propertyAndItemReferences); +#else + s_propertyAndItemReferences.Any(filespec.Contains); +#endif } /// @@ -288,10 +290,10 @@ private static bool ShouldEnforceMatching(string searchPattern) // extensions that start with the same three characters e.g. "*.htm" would match both "file.htm" and "file.html" // 3) if the ? wildcard is to the left of a period, it matches files with shorter name e.g. ???.txt would match // foo.txt, fo.txt and also f.txt - return searchPattern.IndexOf("?.", StringComparison.Ordinal) != -1 || + return searchPattern.Contains("?.") || ( Path.GetExtension(searchPattern).Length == (3 + 1 /* +1 for the period */) && - searchPattern.IndexOf('*') != -1) || + searchPattern.Contains('*')) || searchPattern.EndsWith("?", StringComparison.Ordinal); } @@ -440,7 +442,7 @@ internal static string GetLongPathName( string path, GetFileSystemEntries getFileSystemEntries) { - if (path.IndexOf("~", StringComparison.Ordinal) == -1) + if (!path.Contains('~')) { // A path with no '~' must not be a short name. return path; @@ -451,15 +453,11 @@ internal static string GetLongPathName( string[] parts = path.Split(directorySeparatorCharacters); string pathRoot; - bool isUnc = path.StartsWith(s_directorySeparator + s_directorySeparator, StringComparison.Ordinal); + bool isUnc = path.StartsWith(s_twoDirectorySeparators, StringComparison.Ordinal); int startingElement; if (isUnc) { - pathRoot = s_directorySeparator + s_directorySeparator; - pathRoot += parts[2]; - pathRoot += s_directorySeparator; - pathRoot += parts[3]; - pathRoot += s_directorySeparator; + pathRoot = $"{s_twoDirectorySeparators}{parts[2]}{s_directorySeparatorString}{parts[3]}{s_directorySeparatorString}"; startingElement = 4; } else @@ -468,7 +466,7 @@ internal static string GetLongPathName( if (path.Length > 2 && path[1] == ':') { // Not relative - pathRoot = parts[0] + s_directorySeparator; + pathRoot = parts[0] + s_directorySeparatorString; startingElement = 1; } else @@ -493,7 +491,7 @@ internal static string GetLongPathName( } else { - if (parts[i].IndexOf("~", StringComparison.Ordinal) == -1) + if (!parts[i].Contains('~')) { // If there's no ~, don't hit the disk. longParts[i - startingElement] = parts[i]; @@ -529,7 +527,7 @@ internal static string GetLongPathName( } } - return pathRoot + string.Join(s_directorySeparator, longParts); + return pathRoot + string.Join(s_directorySeparatorString, longParts); } /// @@ -562,8 +560,7 @@ internal void SplitFileSpec( */ if (recursiveDirectoryMatch == filenamePart) { - wildcardDirectoryPart += recursiveDirectoryMatch; - wildcardDirectoryPart += s_directorySeparator; + wildcardDirectoryPart = $"{wildcardDirectoryPart}{recursiveDirectoryMatch}{s_directorySeparatorString}"; filenamePart = "*.*"; } @@ -1107,7 +1104,7 @@ private static RecursiveStepResult GetFilesRecursiveStep( // or we've reached the end of the wildcard directory elements, considerFiles = true; } - else if (recursionState.RemainingWildcardDirectory.IndexOf(recursiveDirectoryMatch, StringComparison.Ordinal) == 0) + else if (recursionState.RemainingWildcardDirectory.StartsWith(recursiveDirectoryMatch, StringComparison.Ordinal)) { // or, we've reached a "**" so everything else is matched recursively. considerFiles = true; @@ -1211,22 +1208,10 @@ internal static string RegularExpressionFromFileSpec( /// /// True if both parts meet all conditions for a legal filespec. private static bool IsLegalFileSpec(string wildcardDirectoryPart, string filenamePart) => - !HasDotDot(wildcardDirectoryPart) + !wildcardDirectoryPart.Contains("..") && !HasMisplacedRecursiveOperator(wildcardDirectoryPart) && !HasMisplacedRecursiveOperator(filenamePart); - private static bool HasDotDot(string str) - { - for (int i = 0; i < str.Length - 1; i++) - { - if (str[i] == '.' && str[i + 1] == '.') - { - return true; - } - } - return false; - } - private static bool HasMisplacedRecursiveOperator(string str) { for (int i = 0; i < str.Length - 1; i++) @@ -1585,7 +1570,7 @@ internal void GetFileSpecInfo( internal static bool RawFileSpecIsValid(string filespec) { // filespec cannot contain illegal characters - if (-1 != filespec.IndexOfAny(s_invalidPathChars)) + if (filespec.AsSpan().IndexOfAny(MSBuildConstants.InvalidPathChars) >= 0) { return false; } @@ -1595,7 +1580,7 @@ internal static bool RawFileSpecIsValid(string filespec) * * Any path with "..." in it is illegal. */ - if (-1 != filespec.IndexOf("...", StringComparison.Ordinal)) + if (filespec.Contains("...")) { return false; } @@ -1607,12 +1592,12 @@ internal static bool RawFileSpecIsValid(string filespec) * http://www.website.com * */ - int rightmostColon = filespec.LastIndexOf(":", StringComparison.Ordinal); + int rightmostColon = filespec.LastIndexOf(':'); if ( - -1 != rightmostColon - && 1 != rightmostColon) + rightmostColon >= 0 + && rightmostColon != 1) { return false; } @@ -2229,7 +2214,7 @@ internal static string Normalize(string aString) // replace multiple slashes with the OS separator else if (afterSlashesIndex > index) { - sb.Append(s_directorySeparator); + sb.Append(s_directorySeparatorString); } // skip non-slashes @@ -2526,7 +2511,7 @@ private static string[] CreateArrayWithSingleItemIfNotExcluded(string filespecUn Debug.Assert(excludeState.SearchData.RegexFileMatch != null || excludeState.SearchData.DirectoryPattern != null, "Expected Regex or directory pattern to be used for exclude file matching"); excludeState.BaseDirectory = state.BaseDirectory; - excludeState.RemainingWildcardDirectory = recursiveDirectoryMatch + s_directorySeparator; + excludeState.RemainingWildcardDirectory = recursiveDirectoryMatch + s_directorySeparatorString; searchesToExclude.Add(excludeState); } } diff --git a/src/Shared/FileUtilities.cs b/src/Shared/FileUtilities.cs index 82d4f55b354..911439a8bb0 100644 --- a/src/Shared/FileUtilities.cs +++ b/src/Shared/FileUtilities.cs @@ -7,6 +7,9 @@ #else using Microsoft.Build.Shared.Concurrent; #endif +#if NET +using System.Buffers; +#endif using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -71,7 +74,7 @@ public static bool GetIsFileSystemCaseSensitive() { try { - string pathWithUpperCase = Path.Combine(Path.GetTempPath(), "CASESENSITIVETEST" + Guid.NewGuid().ToString("N")); + string pathWithUpperCase = Path.Combine(Path.GetTempPath(), $"CASESENSITIVETEST{Guid.NewGuid():N}"); using (new FileStream(pathWithUpperCase, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None, 0x1000, FileOptions.DeleteOnClose)) { string lowerCased = pathWithUpperCase.ToLowerInvariant(); @@ -91,20 +94,24 @@ public static bool GetIsFileSystemCaseSensitive() /// Copied from https://github.com/dotnet/corefx/blob/056715ff70e14712419d82d51c8c50c54b9ea795/src/Common/src/System/IO/PathInternal.Windows.cs#L61 /// MSBuild should support the union of invalid path chars across the supported OSes, so builds can have the same behaviour crossplatform: https://github.com/dotnet/msbuild/issues/781#issuecomment-243942514 /// - internal static readonly char[] InvalidPathChars = +#if NET + internal static readonly SearchValues InvalidPathChars = SearchValues.Create( +#else + internal static readonly char[] InvalidPathChars = ( +#endif [ '|', '\0', (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10, (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20, (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30, (char)31 - ]; + ]); /// /// Copied from https://github.com/dotnet/corefx/blob/387cf98c410bdca8fd195b28cbe53af578698f94/src/System.Runtime.Extensions/src/System/IO/Path.Windows.cs#L18 /// MSBuild should support the union of invalid path chars across the supported OSes, so builds can have the same behaviour crossplatform: https://github.com/dotnet/msbuild/issues/781#issuecomment-243942514 /// - internal static readonly char[] InvalidFileNameChars = + internal static readonly char[] InvalidFileNameCharsArray = [ '\"', '<', '>', '|', '\0', (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10, @@ -113,6 +120,12 @@ public static bool GetIsFileSystemCaseSensitive() (char)31, ':', '*', '?', '\\', '/' ]; +#if NET + internal static readonly SearchValues InvalidFileNameChars = SearchValues.Create(InvalidFileNameCharsArray); +#else + internal static char[] InvalidFileNameChars => InvalidFileNameCharsArray; +#endif + internal static readonly char[] Slashes = { '/', '\\' }; internal static readonly string DirectorySeparatorString = Path.DirectorySeparatorChar.ToString(); @@ -179,7 +192,7 @@ internal static bool CanWriteToDirectory(string directory) { try { - string testFilePath = Path.Combine(directory, $"MSBuild_{Guid.NewGuid().ToString("N")}_testFile.txt"); + string testFilePath = Path.Combine(directory, $"MSBuild_{Guid.NewGuid():N}_testFile.txt"); FileInfo file = new(testFilePath); file.Directory.Create(); // If the directory already exists, this method does nothing. File.WriteAllText(testFilePath, $"MSBuild process {EnvironmentUtilities.CurrentProcessId} successfully wrote to file."); @@ -258,7 +271,11 @@ internal static string EnsureTrailingNoLeadingSlash(string path, int start) return FixFilePath(start < stop && IsSlash(path[stop - 1]) ? path.Substring(start) : +#if NET + string.Concat(path.AsSpan(start), new(in Path.DirectorySeparatorChar))); +#else path.Substring(start) + Path.DirectorySeparatorChar); +#endif } /// @@ -315,7 +332,11 @@ internal static string EnsureQuotes(string path, bool isSingleQuote = true) // Special case: convert the quotes. if (path.Length > 1 && path[0] == convertQuote && path[path.Length - 1] == convertQuote) { +#if NET + path = $"{targetQuote}{path.AsSpan(1, path.Length - 2)}{targetQuote}"; +#else path = $"{targetQuote}{path.Substring(1, path.Length - 2)}{targetQuote}"; +#endif } // Enclose the path in a set of the 'target' quote unless the string is already quoted with the 'target' quotes. else if (path.Length == 1 || path[0] != targetQuote || path[path.Length - 1] != targetQuote) @@ -844,17 +865,23 @@ internal static string NormalizePathForComparisonNoThrow(string path, string cur internal static bool PathIsInvalid(string path) { - if (path.IndexOfAny(InvalidPathChars) >= 0) - { - return true; - } - // Path.GetFileName does not react well to malformed filenames. // For example, Path.GetFileName("a/b/foo:bar") returns bar instead of foo:bar // It also throws exceptions on illegal path characters - var lastDirectorySeparator = path.LastIndexOfAny(Slashes); - - return path.IndexOfAny(InvalidFileNameChars, lastDirectorySeparator >= 0 ? lastDirectorySeparator + 1 : 0) >= 0; +#if NET + if (!path.AsSpan().ContainsAny(InvalidPathChars)) + { + int lastDirectorySeparator = path.LastIndexOfAny(Slashes); + return path.AsSpan(lastDirectorySeparator >= 0 ? lastDirectorySeparator + 1 : 0).ContainsAny(InvalidFileNameChars); + } +#else + if (path.IndexOfAny(InvalidPathChars) < 0) + { + int lastDirectorySeparator = path.LastIndexOfAny(Slashes); + return path.IndexOfAny(InvalidFileNameChars, lastDirectorySeparator >= 0 ? lastDirectorySeparator + 1 : 0) >= 0; + } +#endif + return true; } /// @@ -1538,12 +1565,20 @@ internal static void ClearFileExistenceCache() internal static void ReadFromStream(this Stream stream, byte[] content, int startIndex, int length) { -#if NET7_0_OR_GREATER +#if NET stream.ReadExactly(content, startIndex, length); #else -#pragma warning disable CA2022 // Avoid inexact read with 'Stream.Read' - stream.Read(content, 0, length); -#pragma warning restore CA2022 // Avoid inexact read with 'Stream.Read' + int bytesRead = 0; + while (bytesRead < length) + { + int read = stream.Read(content, startIndex + bytesRead, length - bytesRead); + if (read == 0) + { + throw new EndOfStreamException(); + } + + bytesRead += read; + } #endif } } diff --git a/src/Shared/FileUtilitiesRegex.cs b/src/Shared/FileUtilitiesRegex.cs index 76e283a1a2a..c35f1f9ed5f 100644 --- a/src/Shared/FileUtilitiesRegex.cs +++ b/src/Shared/FileUtilitiesRegex.cs @@ -52,7 +52,11 @@ internal static bool StartsWithDrivePattern(string pattern) // first character must be a letter, // second character must be a ":" return pattern.Length >= 2 && +#if NET + char.IsAsciiLetter(pattern[0]) && +#else ((pattern[0] >= 'A' && pattern[0] <= 'Z') || (pattern[0] >= 'a' && pattern[0] <= 'z')) && +#endif pattern[1] == ':'; } diff --git a/src/Shared/FrameworkLocationHelper.cs b/src/Shared/FrameworkLocationHelper.cs index 2bc28819c6e..458272bcc30 100644 --- a/src/Shared/FrameworkLocationHelper.cs +++ b/src/Shared/FrameworkLocationHelper.cs @@ -97,7 +97,7 @@ internal static class FrameworkLocationHelper private const string dotNetFrameworkRegistryKeyV20 = dotNetFrameworkSetupRegistryPath + "\\" + dotNetFrameworkVersionV20; internal static string dotNetFrameworkVersionFolderPrefixV30 = NativeMethodsShared.IsWindows ? "v3.0" : "3.0"; // v3.0 is for WinFx. - private static string s_dotNetFrameworkRegistryKeyV30 = dotNetFrameworkSetupRegistryPath + "\\" + dotNetFrameworkVersionFolderPrefixV30 + "\\Setup"; + private static readonly string s_dotNetFrameworkRegistryKeyV30 = dotNetFrameworkSetupRegistryPath + "\\" + dotNetFrameworkVersionFolderPrefixV30 + "\\Setup"; #if FEATURE_WIN32_REGISTRY private const string fallbackDotNetFrameworkSdkRegistryInstallPath = "SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows"; @@ -111,7 +111,7 @@ internal static class FrameworkLocationHelper private const string fullDotNetFrameworkSdkRegistryPathForV35ToolsOnManagedToolsSDK80A = "HKEY_LOCAL_MACHINE\\" + dotNetFrameworkSdkRegistryPathForV35ToolsOnManagedToolsSDK80A; internal static string dotNetFrameworkVersionFolderPrefixV35 = NativeMethodsShared.IsWindows ? "v3.5" : "3.5"; // v3.5 is for Orcas. - private static string s_dotNetFrameworkRegistryKeyV35 = dotNetFrameworkSetupRegistryPath + "\\" + dotNetFrameworkVersionFolderPrefixV35; + private static readonly string s_dotNetFrameworkRegistryKeyV35 = dotNetFrameworkSetupRegistryPath + "\\" + dotNetFrameworkVersionFolderPrefixV35; internal const string fullDotNetFrameworkSdkRegistryKeyV35OnVS10 = fullDotNetFrameworkSdkRegistryPathForV35ToolsOnWinSDK70A; internal const string fullDotNetFrameworkSdkRegistryKeyV35OnVS11 = fullDotNetFrameworkSdkRegistryPathForV35ToolsOnManagedToolsSDK80A; @@ -512,7 +512,7 @@ private static string FallbackDotNetFrameworkSdkInstallPath fallbackDotNetFrameworkSdkRegistryInstallPath, fallbackDotNetFrameworkSdkInstallKeyValue); - if (EnvironmentUtilities.Is64BitProcess && s_fallbackDotNetFrameworkSdkInstallPath == null) + if (Environment.Is64BitProcess && s_fallbackDotNetFrameworkSdkInstallPath == null) { // Since we're 64-bit, what we just checked was the 64-bit fallback key -- so now let's // check the 32-bit one too, just in case. @@ -773,8 +773,7 @@ internal static string FindDotNetFrameworkPath( { if (!NativeMethodsShared.IsWindows) { - if (!string.IsNullOrEmpty(prefix) - && prefix.Substring(0, 1).Equals("v", StringComparison.OrdinalIgnoreCase)) + if (!string.IsNullOrEmpty(prefix) && prefix[0] is 'v' or 'V') { prefix = prefix.Substring(1); } @@ -813,8 +812,12 @@ internal static string FindDotNetFrameworkPath( // the path is something like 'C:\MyPath\64\Framework64'. 9 = length of 'Framework', to make the index match // the location of the '64'. int indexOf64 = indexOfFramework64 + 9; - string tempLocation = baseLocation; - baseLocation = tempLocation.Substring(0, indexOf64) + tempLocation.Substring(indexOf64 + 2, tempLocation.Length - indexOf64 - 2); + baseLocation = +#if NET + string.Concat(baseLocation.AsSpan(0, indexOf64), baseLocation.AsSpan(indexOf64 + 2)); +#else + baseLocation.Substring(0, indexOf64) + baseLocation.Substring(indexOf64 + 2); +#endif } else if (indexOfFramework64 == -1 && architecture == DotNetFrameworkArchitecture.Bitness64) { diff --git a/src/Shared/LogMessagePacketBase.cs b/src/Shared/LogMessagePacketBase.cs index 04216a42dad..3c617c54014 100644 --- a/src/Shared/LogMessagePacketBase.cs +++ b/src/Shared/LogMessagePacketBase.cs @@ -274,12 +274,13 @@ internal abstract class LogMessagePacketBase : INodePacket /// /// Dictionary of methods used to read BuildEventArgs. /// - private static Dictionary s_readMethodCache = new Dictionary(); + private static readonly Dictionary s_readMethodCache = new Dictionary(); + #endif /// /// Dictionary of methods used to write BuildEventArgs. /// - private static Dictionary s_writeMethodCache = new Dictionary(); + private static readonly Dictionary s_writeMethodCache = new Dictionary(); /// /// Delegate for translating targetfinished events. diff --git a/src/Shared/MSBuildNameIgnoreCaseComparer.cs b/src/Shared/MSBuildNameIgnoreCaseComparer.cs index 9517b5f2646..e8e9a65b9eb 100644 --- a/src/Shared/MSBuildNameIgnoreCaseComparer.cs +++ b/src/Shared/MSBuildNameIgnoreCaseComparer.cs @@ -64,6 +64,9 @@ public bool Equals(string compareToString, string constrainedString, int start, return false; } +#if NET + return compareToString.AsSpan().Equals(constrainedString.AsSpan(start, lengthToCompare), StringComparison.OrdinalIgnoreCase); +#else if (lengthToCompare != compareToString.Length) { return false; @@ -104,6 +107,7 @@ public bool Equals(string compareToString, string constrainedString, int start, } return true; +#endif } /// diff --git a/src/Shared/Modifiers.cs b/src/Shared/Modifiers.cs index a6c203525d2..d7e77955644 100644 --- a/src/Shared/Modifiers.cs +++ b/src/Shared/Modifiers.cs @@ -69,7 +69,7 @@ internal static class ItemSpecModifiers DefiningProjectExtension }; - private static HashSet s_tableOfItemSpecModifiers = new HashSet(All, StringComparer.OrdinalIgnoreCase); + private static readonly HashSet s_tableOfItemSpecModifiers = new HashSet(All, StringComparer.OrdinalIgnoreCase); /// /// Indicates if the given name is reserved for an item-spec modifier. diff --git a/src/Shared/NodeEndpointOutOfProcBase.cs b/src/Shared/NodeEndpointOutOfProcBase.cs index 70629ecf2d8..846f7716ec4 100644 --- a/src/Shared/NodeEndpointOutOfProcBase.cs +++ b/src/Shared/NodeEndpointOutOfProcBase.cs @@ -494,7 +494,7 @@ private void PacketPumpProc() { if (localPipeServer.IsConnected) { -#if NETCOREAPP // OperatingSystem.IsWindows() is new in .NET 5.0 +#if NET // OperatingSystem.IsWindows() is new in .NET 5.0 if (OperatingSystem.IsWindows()) #endif { @@ -522,7 +522,7 @@ private void RunReadLoop(BufferedReadStream localReadPipe, NamedPipeServerStream #if NET451_OR_GREATER Task readTask = localReadPipe.ReadAsync(headerByte, 0, headerByte.Length, CancellationToken.None); #elif NETCOREAPP - Task readTask = CommunicationsUtilities.ReadAsync(localReadPipe, headerByte, headerByte.Length); + Task readTask = CommunicationsUtilities.ReadAsync(localReadPipe, headerByte, headerByte.Length).AsTask(); #else IAsyncResult result = localReadPipe.BeginRead(headerByte, 0, headerByte.Length, null, null); #endif @@ -614,7 +614,7 @@ private void RunReadLoop(BufferedReadStream localReadPipe, NamedPipeServerStream #if NET451_OR_GREATER readTask = localReadPipe.ReadAsync(headerByte, 0, headerByte.Length, CancellationToken.None); #elif NETCOREAPP - readTask = CommunicationsUtilities.ReadAsync(localReadPipe, headerByte, headerByte.Length); + readTask = CommunicationsUtilities.ReadAsync(localReadPipe, headerByte, headerByte.Length).AsTask(); #else result = localReadPipe.BeginRead(headerByte, 0, headerByte.Length, null, null); #endif diff --git a/src/Shared/PlatformNegotiation.cs b/src/Shared/PlatformNegotiation.cs index 18b9b361e3b..c21db16aa0e 100644 --- a/src/Shared/PlatformNegotiation.cs +++ b/src/Shared/PlatformNegotiation.cs @@ -61,18 +61,18 @@ internal static string GetNearestPlatform(string overridePlatformValue, string r // Prioritize platformLookupTable **metadata** attached to the ProjectReference item // before the current project's table. We do this to allow per-ProjectReference fine tuning. else if (projectReferenceLookupTable != null && - projectReferenceLookupTable.ContainsKey(currentProjectPlatform) && - projectReferencePlatforms.Contains(projectReferenceLookupTable[currentProjectPlatform])) + projectReferenceLookupTable.TryGetValue(currentProjectPlatform, out string? value) && + projectReferencePlatforms.Contains(value)) { - buildProjectReferenceAs = projectReferenceLookupTable[currentProjectPlatform]; + buildProjectReferenceAs = value; log?.LogMessageFromResources(MessageImportance.Low, "GetCompatiblePlatform.FoundMappingInTable", currentProjectPlatform, buildProjectReferenceAs, projectReferenceLookupTableMetadata); } // Current project's translation table follows else if (currentProjectLookupTable != null && - currentProjectLookupTable.ContainsKey(currentProjectPlatform) && - projectReferencePlatforms.Contains(currentProjectLookupTable[currentProjectPlatform])) + currentProjectLookupTable.TryGetValue(currentProjectPlatform, out value) && + projectReferencePlatforms.Contains(value)) { - buildProjectReferenceAs = currentProjectLookupTable[currentProjectPlatform]; + buildProjectReferenceAs = value; log?.LogMessageFromResources(MessageImportance.Low, "GetCompatiblePlatform.FoundMappingInTable", currentProjectPlatform, buildProjectReferenceAs, platformLookupTable); } // AnyCPU if possible diff --git a/src/Shared/ProcessExtensions.cs b/src/Shared/ProcessExtensions.cs index 362a8b0a8c1..d13ebbac5fe 100644 --- a/src/Shared/ProcessExtensions.cs +++ b/src/Shared/ProcessExtensions.cs @@ -11,7 +11,7 @@ internal static class ProcessExtensions { public static void KillTree(this Process process, int timeoutMilliseconds) { -#if NETCOREAPP +#if NET process.Kill(entireProcessTree: true); #else if (NativeMethodsShared.IsWindows) diff --git a/src/Shared/ProjectWriter.cs b/src/Shared/ProjectWriter.cs index fa2e6f76a1c..d5a5a00003f 100644 --- a/src/Shared/ProjectWriter.cs +++ b/src/Shared/ProjectWriter.cs @@ -15,7 +15,7 @@ namespace Microsoft.Build.Shared /// This class is used to save MSBuild project files. It contains special handling for MSBuild notations that are not saved /// correctly by the XML DOM's default save mechanism. /// - internal sealed class ProjectWriter : XmlTextWriter + internal sealed partial class ProjectWriter : XmlTextWriter { #region Regular expressions for item vector transforms @@ -28,9 +28,7 @@ internal sealed class ProjectWriter : XmlTextWriter // Note that the pattern is more strict than the rules for valid XML element names. internal const string itemTypeOrMetadataNameSpecification = @"[A-Za-z_][A-Za-z_0-9\-]*"; - // the portion of an item transform that is the function that we wish to execute on the item - internal const string itemFunctionNameSpecification = @"[A-Za-z]*"; - + // regular expression used to match item vector transforms // description of an item vector transform, including the optional separator specification private const string itemVectorTransformSpecification = @"(?@\(\s*) @@ -40,15 +38,9 @@ internal sealed class ProjectWriter : XmlTextWriter (?\s*\))"; // ) - // regular expression used to match item vector transforms - // internal for unit testing only - internal static readonly Lazy itemVectorTransformPattern = new Lazy( - () => - new Regex(itemVectorTransformSpecification, - RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture | RegexOptions.Compiled)); - // description of an item vector transform, including the optional separator specification, but with no (named) capturing // groups -- see the WriteString() method for details + // regular expression used to match item vector transforms, with no (named) capturing groups private const string itemVectorTransformRawSpecification = @"@\(\s* (" + itemTypeOrMetadataNameSpecification + @") @@ -56,12 +48,21 @@ internal sealed class ProjectWriter : XmlTextWriter (\s*,\s*'[^']*')? \s*\)"; - // regular expression used to match item vector transforms, with no (named) capturing groups - // internal for unit testing only - internal static readonly Lazy itemVectorTransformRawPattern = new Lazy( - () => - new Regex(itemVectorTransformRawSpecification, - RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture | RegexOptions.Compiled)); +#if NET + [GeneratedRegex(itemVectorTransformSpecification, RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture)] + private static partial Regex ItemVectorTransformRegex { get; } + + [GeneratedRegex(itemVectorTransformRawSpecification, RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture)] + private static partial Regex ItemVectorTransformRawRegex { get; } +#else + private static Regex ItemVectorTransformRegex => itemVectorTransformPattern ??= + new Regex(itemVectorTransformSpecification, RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture | RegexOptions.Compiled); + private static Regex itemVectorTransformPattern; + + private static Regex ItemVectorTransformRawRegex => itemVectorTransformRawPattern ??= + new Regex(itemVectorTransformRawSpecification, RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture | RegexOptions.Compiled); + private static Regex itemVectorTransformRawPattern; +#endif /************************************************************************************************************************** * WARNING: The regular expressions above MUST be kept in sync with the expressions in the ItemExpander class. @@ -130,14 +131,14 @@ internal void Initialize(XmlDocument project, XmlDeclaration projectRootElementD /// public override void WriteString(string text) { - MatchCollection itemVectorTransforms = itemVectorTransformRawPattern.Value.Matches(text); + MatchCollection itemVectorTransforms = ItemVectorTransformRawRegex.Matches(text); // if the string contains any item vector transforms if (itemVectorTransforms.Count > 0) { // separate out the text that surrounds the transforms // NOTE: use the Regex with no (named) capturing groups, otherwise Regex.Split() will split on them - string[] surroundingTextPieces = itemVectorTransformRawPattern.Value.Split(text); + string[] surroundingTextPieces = ItemVectorTransformRawRegex.Split(text); ErrorUtilities.VerifyThrow(itemVectorTransforms.Count == (surroundingTextPieces.Length - 1), "We must have two pieces of surrounding text for every item vector transform found."); @@ -149,7 +150,7 @@ public override void WriteString(string text) base.WriteString(surroundingTextPieces[i]); // break up the transform into its constituent pieces - Match itemVectorTransform = itemVectorTransformPattern.Value.Match(itemVectorTransforms[i].Value); + Match itemVectorTransform = ItemVectorTransformRegex.Match(itemVectorTransforms[i].Value); ErrorUtilities.VerifyThrow(itemVectorTransform.Success, "Item vector transform must be matched by both the raw and decorated regular expressions."); diff --git a/src/Shared/PropertyParser.cs b/src/Shared/PropertyParser.cs index a9e1c29d72c..48110e0ca4c 100644 --- a/src/Shared/PropertyParser.cs +++ b/src/Shared/PropertyParser.cs @@ -47,10 +47,10 @@ internal static bool GetTable(TaskLoggingHelper log, string parameterName, strin // whitespace from beginning and end of both name and value. (When authoring a // project/targets file, people like to use whitespace and newlines to pretty up // the file format.) - if (indexOfEqualsSign != -1) + if (indexOfEqualsSign >= 0) { - propertyName = propertyNameValuePair.Substring(0, indexOfEqualsSign).Trim(); - propertyValue = propertyNameValuePair.Substring(indexOfEqualsSign + 1).Trim(); + propertyName = propertyNameValuePair.AsSpan(0, indexOfEqualsSign).Trim().ToString(); + propertyValue = propertyNameValuePair.AsSpan(indexOfEqualsSign + 1).Trim().ToString(); } // Make sure we have a property name and property value (though the value is allowed to be blank). @@ -103,8 +103,8 @@ internal static bool GetTableWithEscaping(TaskLoggingHelper log, string paramete // whitespace from beginning and end of both name and value. (When authoring a // project/targets file, people like to use whitespace and newlines to pretty up // the file format.) - string propertyName = propertyNameValueString.Substring(0, indexOfEqualsSign).Trim(); - string propertyValue = EscapingUtilities.Escape(propertyNameValueString.Substring(indexOfEqualsSign + 1).Trim()); + string propertyName = propertyNameValueString.AsSpan(0, indexOfEqualsSign).Trim().ToString(); + string propertyValue = EscapingUtilities.Escape(propertyNameValueString.AsSpan(indexOfEqualsSign + 1).Trim().ToString()); // Make sure we have a property name and property value (though the value is allowed to be blank). if (propertyName.Length == 0) diff --git a/src/Shared/RegisteredTaskObjectCacheBase.cs b/src/Shared/RegisteredTaskObjectCacheBase.cs index d7d25b7d962..10391f5d336 100644 --- a/src/Shared/RegisteredTaskObjectCacheBase.cs +++ b/src/Shared/RegisteredTaskObjectCacheBase.cs @@ -22,7 +22,7 @@ internal class RegisteredTaskObjectCacheBase /// /// The cache for AppDomain lifetime objects. /// - private static Lazy> s_appDomainLifetimeObjects = new Lazy>(); + private static readonly Lazy> s_appDomainLifetimeObjects = new Lazy>(); /// /// The cache for Build lifetime objects. diff --git a/src/Shared/ResourceUtilities.cs b/src/Shared/ResourceUtilities.cs index e046c7d4c5a..7ff74c83f19 100644 --- a/src/Shared/ResourceUtilities.cs +++ b/src/Shared/ResourceUtilities.cs @@ -122,7 +122,7 @@ internal static string ExtractMessageCode(bool msbuildCodeOnly, string message, if (i < message.Length) { - message = message.Substring(i, message.Length - i); + message = message.Substring(i); } return message; diff --git a/src/Shared/StringUtils.cs b/src/Shared/StringUtils.cs index 10152956f27..cb109d5f9d6 100644 --- a/src/Shared/StringUtils.cs +++ b/src/Shared/StringUtils.cs @@ -16,6 +16,10 @@ internal static class StringUtils /// Random generated string of the specified length. public static string GenerateRandomString(int length) { +#if NET + return string.Create(length, 0, static (dest, _) => + Random.Shared.GetItems("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+_", dest)); +#else // Base64, 2^6 = 64 const int eachStringCharEncodesBites = 6; const int eachByteHasBits = 8; @@ -32,6 +36,7 @@ public static string GenerateRandomString(int length) string randomBase64String = Convert.ToBase64String(randomBytes).Replace('/', '_'); return randomBase64String.Substring(0, length); +#endif } /// @@ -45,10 +50,14 @@ public static string RemoveLastInstanceOf(this string fromString, string substri { int lastOccurrenceIndex = fromString.LastIndexOf(substring, comparison); - if (lastOccurrenceIndex != -1) + if (lastOccurrenceIndex >= 0) { - fromString = fromString.Substring(0, lastOccurrenceIndex) + - fromString.Substring(lastOccurrenceIndex + substring.Length); + fromString = +#if NET + $"{fromString.AsSpan(0, lastOccurrenceIndex)}{fromString.AsSpan(lastOccurrenceIndex + substring.Length)}"; +#else + $"{fromString.Substring(0, lastOccurrenceIndex)}{fromString.Substring(lastOccurrenceIndex + substring.Length)}"; +#endif } return fromString; diff --git a/src/Shared/TempFileUtilities.cs b/src/Shared/TempFileUtilities.cs index 190f0dddf2b..4c607dfd2b4 100644 --- a/src/Shared/TempFileUtilities.cs +++ b/src/Shared/TempFileUtilities.cs @@ -82,7 +82,7 @@ private static string CreateFolderUnderTemp() /// internal static string GetTemporaryDirectory(bool createDirectory = true, string subfolder = null) { - string temporaryDirectory = Path.Combine(TempFileDirectory, "Temporary" + Guid.NewGuid().ToString("N"), subfolder ?? string.Empty); + string temporaryDirectory = Path.Combine(TempFileDirectory, $"Temporary{Guid.NewGuid():N}", subfolder ?? string.Empty); if (createDirectory) { diff --git a/src/Shared/ToolsetElement.cs b/src/Shared/ToolsetElement.cs index 0e2f7591b55..70002db51f0 100644 --- a/src/Shared/ToolsetElement.cs +++ b/src/Shared/ToolsetElement.cs @@ -279,7 +279,7 @@ private void UpdateOSMap(ConfigurationElement element) { if (element.ElementInformation.LineNumber != 0) { - locationString = String.Format("{0} ({1})", element.ElementInformation.Source, element.ElementInformation.LineNumber); + locationString = $"{element.ElementInformation.Source} ({element.ElementInformation.LineNumber})"; } else { diff --git a/src/Shared/Tracing.cs b/src/Shared/Tracing.cs index 87b3a3b7e67..154c6ff6f05 100644 --- a/src/Shared/Tracing.cs +++ b/src/Shared/Tracing.cs @@ -25,7 +25,7 @@ internal static class Tracing /// /// A dictionary of named counters /// - private static Dictionary s_counts; + private static readonly Dictionary s_counts; /// /// Last time logging happened @@ -35,7 +35,7 @@ internal static class Tracing /// /// How often to log /// - private static TimeSpan s_interval; + private static readonly TimeSpan s_interval; /// /// A place callers can put something worth logging later @@ -45,7 +45,7 @@ internal static class Tracing /// /// Short name of the current assembly - to distinguish statics when this type is shared into different assemblies /// - private static string s_currentAssemblyName; + private static readonly string s_currentAssemblyName; #pragma warning restore 649 #if DEBUG @@ -95,7 +95,7 @@ internal static void Slot(string tag, string value) [Conditional("DEBUG")] internal static void Slot(string tag, KeyValuePair value) { - Slot(tag, value.Key.ToString() + "=" + value.Key.ToString()); + Slot(tag, $"{value.Key}={value.Key}"); } /// diff --git a/src/Shared/TypeLoader.cs b/src/Shared/TypeLoader.cs index 3a3013b36aa..7c5efa8ce3d 100644 --- a/src/Shared/TypeLoader.cs +++ b/src/Shared/TypeLoader.cs @@ -31,12 +31,12 @@ internal class TypeLoader /// /// Cache to keep track of the assemblyLoadInfos based on a given type filter. /// - private static ConcurrentDictionary, ConcurrentDictionary> s_cacheOfLoadedTypesByFilter = new ConcurrentDictionary, ConcurrentDictionary>(); + private static readonly ConcurrentDictionary, ConcurrentDictionary> s_cacheOfLoadedTypesByFilter = new ConcurrentDictionary, ConcurrentDictionary>(); /// /// Cache to keep track of the assemblyLoadInfos based on a given type filter for assemblies which are to be loaded for reflectionOnlyLoads. /// - private static ConcurrentDictionary, ConcurrentDictionary> s_cacheOfReflectionOnlyLoadedTypesByFilter = new ConcurrentDictionary, ConcurrentDictionary>(); + private static readonly ConcurrentDictionary, ConcurrentDictionary> s_cacheOfReflectionOnlyLoadedTypesByFilter = new ConcurrentDictionary, ConcurrentDictionary>(); /// /// Type filter for this typeloader @@ -45,7 +45,7 @@ internal class TypeLoader private static MetadataLoadContext _context; - private static string[] runtimeAssemblies = findRuntimeAssembliesWithMicrosoftBuildFramework(); + private static readonly string[] runtimeAssemblies = findRuntimeAssembliesWithMicrosoftBuildFramework(); private static string microsoftBuildFrameworkPath; // We need to append Microsoft.Build.Framework from next to the executing assembly first to make sure it's loaded before the runtime variant. @@ -56,10 +56,7 @@ private static string[] findRuntimeAssembliesWithMicrosoftBuildFramework() string[] msbuildAssemblies = Directory.GetFiles(msbuildDirectory, "*.dll"); string[] runtimeAssemblies = Directory.GetFiles(RuntimeEnvironment.GetRuntimeDirectory(), "*.dll"); - List runtimeAssembliesList = new(runtimeAssemblies); - runtimeAssembliesList.AddRange(msbuildAssemblies); - - return runtimeAssembliesList.ToArray(); + return [.. runtimeAssemblies, .. msbuildAssemblies]; } /// diff --git a/src/Shared/UnitTests/NativeMethodsShared_Tests.cs b/src/Shared/UnitTests/NativeMethodsShared_Tests.cs index efb5d7297ab..b96ee0700d4 100644 --- a/src/Shared/UnitTests/NativeMethodsShared_Tests.cs +++ b/src/Shared/UnitTests/NativeMethodsShared_Tests.cs @@ -123,7 +123,7 @@ public void SetCurrentDirectoryDoesNotSetNonexistentFolder() { for (int i = 0; i < 10; i++) { - nonexistentDirectory = Path.Combine(currentDirectory, "foo", "bar", "baz") + Guid.NewGuid(); + nonexistentDirectory = $"{Path.Combine(currentDirectory, "foo", "bar", "baz")}{Guid.NewGuid()}"; if (!Directory.Exists(nonexistentDirectory)) { diff --git a/src/Shared/UnitTests/XmakeAttributes_Tests.cs b/src/Shared/UnitTests/XmakeAttributes_Tests.cs index 25e7cb3a50d..0ccd5682302 100644 --- a/src/Shared/UnitTests/XmakeAttributes_Tests.cs +++ b/src/Shared/UnitTests/XmakeAttributes_Tests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using Microsoft.Build.Shared; using Shouldly; @@ -130,7 +131,7 @@ public void TestMergeRuntimeValuesCurrentToCore() public void TestArchitectureValuesMatch() { string currentArchitecture = XMakeAttributes.GetCurrentMSBuildArchitecture(); - string notCurrentArchitecture = EnvironmentUtilities.Is64BitProcess ? XMakeAttributes.MSBuildArchitectureValues.x86 : XMakeAttributes.MSBuildArchitectureValues.x64; + string notCurrentArchitecture = Environment.Is64BitProcess ? XMakeAttributes.MSBuildArchitectureValues.x86 : XMakeAttributes.MSBuildArchitectureValues.x64; Assert.True(XMakeAttributes.ArchitectureValuesMatch(XMakeAttributes.MSBuildArchitectureValues.any, XMakeAttributes.MSBuildArchitectureValues.currentArchitecture)); Assert.True(XMakeAttributes.ArchitectureValuesMatch(XMakeAttributes.MSBuildArchitectureValues.any, XMakeAttributes.MSBuildArchitectureValues.x64)); @@ -145,7 +146,7 @@ public void TestArchitectureValuesMatch() public void TestMergeArchitectureValues() { string currentArchitecture = XMakeAttributes.GetCurrentMSBuildArchitecture(); - string notCurrentArchitecture = EnvironmentUtilities.Is64BitProcess ? XMakeAttributes.MSBuildArchitectureValues.x86 : XMakeAttributes.MSBuildArchitectureValues.x64; + string notCurrentArchitecture = Environment.Is64BitProcess ? XMakeAttributes.MSBuildArchitectureValues.x86 : XMakeAttributes.MSBuildArchitectureValues.x64; string mergedArchitecture; Assert.True(XMakeAttributes.TryMergeArchitectureValues(XMakeAttributes.MSBuildArchitectureValues.any, XMakeAttributes.MSBuildArchitectureValues.currentArchitecture, out mergedArchitecture)); diff --git a/src/Shared/VersionUtilities.cs b/src/Shared/VersionUtilities.cs index 91c4721f00c..99e1e36774d 100644 --- a/src/Shared/VersionUtilities.cs +++ b/src/Shared/VersionUtilities.cs @@ -74,7 +74,7 @@ internal static Version ConvertToVersion(string version, bool throwException) // Versions must have at least a Major and a Minor (e.g. 10.0), so if it's // just one number without a decimal, add a decimal and a 0. Random strings // like "tmp" will be filtered out in the Parse() or TryParse() steps - if (version.IndexOf(".") == -1) + if (version.IndexOf('.') == -1) { version += ".0"; } diff --git a/src/Shared/XMakeElements.cs b/src/Shared/XMakeElements.cs index 991feb5796c..40d22e96234 100644 --- a/src/Shared/XMakeElements.cs +++ b/src/Shared/XMakeElements.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Buffers; using System.Collections.Generic; #nullable disable @@ -35,7 +36,12 @@ internal static class XMakeElements internal const string usingTaskBody = "Task"; internal const string sdk = "Sdk"; - internal static readonly char[] InvalidTargetNameCharacters = [ '$', '@', '(', ')', '%', '*', '?', '.' ]; +#if NET + internal static readonly SearchValues InvalidTargetNameCharacters = SearchValues.Create( +#else + internal static readonly char[] InvalidTargetNameCharacters = ( +#endif + ['$', '@', '(', ')', '%', '*', '?', '.']); // Names that cannot be used as property or item names because they are reserved internal static readonly HashSet ReservedItemNames = diff --git a/src/StringTools/InternableString.cs b/src/StringTools/InternableString.cs index 7e657d56cdb..d19afe2a662 100644 --- a/src/StringTools/InternableString.cs +++ b/src/StringTools/InternableString.cs @@ -387,7 +387,7 @@ private static unsafe uint GetHashCodeHelper(char* charPtr, int length, uint has [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint RotateLeft(uint value, int offset) { -#if NETCOREAPP +#if NET return System.Numerics.BitOperations.RotateLeft(value, offset); #else // Copied from System\Numerics\BitOperations.cs in dotnet/runtime as the routine is not available on .NET Framework. diff --git a/src/StringTools/WeakStringCacheInterner.cs b/src/StringTools/WeakStringCacheInterner.cs index 2eb3fd23231..cc53c501ce3 100644 --- a/src/StringTools/WeakStringCacheInterner.cs +++ b/src/StringTools/WeakStringCacheInterner.cs @@ -85,7 +85,7 @@ public string InternableToString(ref InternableString candidate) string expectedString = candidate.ExpensiveConvertToString(); if (!String.Equals(internedString, expectedString)) { - throw new InvalidOperationException(String.Format("Interned string {0} should have been {1}", internedString, expectedString)); + throw new InvalidOperationException($"Interned string {internedString} should have been {expectedString}"); } #endif @@ -137,12 +137,12 @@ public string FormatStatistics() if (_internCallCountsByString != null) { - result.AppendLine(string.Format("\n{0}{1}{0}", new string('=', 41 - (title.Length / 2)), title)); - result.AppendLine(string.Format("||{0,50}|{1,20:N0}|{2,8}|", "WeakStringCache Hits", _regularInternHits, "hits")); - result.AppendLine(string.Format("||{0,50}|{1,20:N0}|{2,8}|", "WeakStringCache Misses", _regularInternMisses, "misses")); - result.AppendLine(string.Format("||{0,50}|{1,20:N0}|{2,8}|", "Eliminated Strings*", _internEliminatedStrings, "strings")); - result.AppendLine(string.Format("||{0,50}|{1,20:N0}|{2,8}|", "Eliminated Chars", _internEliminatedChars, "chars")); - result.AppendLine(string.Format("||{0,50}|{1,20:N0}|{2,8}|", "Estimated Eliminated Bytes", _internEliminatedChars * 2, "bytes")); + result.AppendLine($"\n{new string('=', 41 - (title.Length / 2))}{title}{new string('=', 41 - (title.Length / 2))}"); + result.AppendLine($"||{"WeakStringCache Hits",50}|{_regularInternHits,20:N0}|{"hits",8}|"); + result.AppendLine($"||{"WeakStringCache Misses",50}|{_regularInternMisses,20:N0}|{"misses",8}|"); + result.AppendLine($"||{"Eliminated Strings*",50}|{_internEliminatedStrings,20:N0}|{"strings",8}|"); + result.AppendLine($"||{"Eliminated Chars",50}|{_internEliminatedChars,20:N0}|{"chars",8}|"); + result.AppendLine($"||{"Estimated Eliminated Bytes",50}|{_internEliminatedChars * 2,20:N0}|{"bytes",8}|"); result.AppendLine("Elimination assumes that strings provided were unique objects."); result.AppendLine("|---------------------------------------------------------------------------------|"); @@ -158,7 +158,7 @@ public string FormatStatistics() WeakStringCache.DebugInfo debugInfo = _weakStringCache.GetDebugInfo(); result.AppendLine("WeakStringCache statistics:"); - result.AppendLine(string.Format("String count live/collected/total = {0}/{1}/{2}", debugInfo.LiveStringCount, debugInfo.CollectedStringCount, debugInfo.LiveStringCount + debugInfo.CollectedStringCount)); + result.AppendLine($"String count live/collected/total = {debugInfo.LiveStringCount}/{debugInfo.CollectedStringCount}/{debugInfo.LiveStringCount + debugInfo.CollectedStringCount}"); } else { diff --git a/src/Tasks/AppConfig/BindingRedirect.cs b/src/Tasks/AppConfig/BindingRedirect.cs index 8be7d2413c7..d29808b9b59 100644 --- a/src/Tasks/AppConfig/BindingRedirect.cs +++ b/src/Tasks/AppConfig/BindingRedirect.cs @@ -43,17 +43,21 @@ internal void Read(XmlReader reader) try { - if (dashPosition != -1) + if (dashPosition >= 0) { // This is a version range. - OldVersionLow = new Version(oldVersion.Substring(0, dashPosition)); - OldVersionHigh = new Version(oldVersion.Substring(dashPosition + 1)); +#if NET + OldVersionLow = Version.Parse(oldVersion.AsSpan(0, dashPosition)); + OldVersionHigh = Version.Parse(oldVersion.AsSpan(dashPosition + 1)); +#else + OldVersionLow = Version.Parse(oldVersion.Substring(0, dashPosition)); + OldVersionHigh = Version.Parse(oldVersion.Substring(dashPosition + 1)); +#endif } else { // This is a single version. - OldVersionLow = new Version(oldVersion); - OldVersionHigh = new Version(oldVersion); + OldVersionLow = OldVersionHigh = new Version(oldVersion); } } catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) diff --git a/src/Tasks/AssemblyDependency/AssemblyFoldersFromConfig/AssemblyFoldersFromConfigCache.cs b/src/Tasks/AssemblyDependency/AssemblyFoldersFromConfig/AssemblyFoldersFromConfigCache.cs index 1453e8ab6d8..ded0c8b46bf 100644 --- a/src/Tasks/AssemblyDependency/AssemblyFoldersFromConfig/AssemblyFoldersFromConfigCache.cs +++ b/src/Tasks/AssemblyDependency/AssemblyFoldersFromConfig/AssemblyFoldersFromConfigCache.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections.Immutable; +using System.Collections.Generic; using System.IO; using System.Linq; using Microsoft.Build.Shared; @@ -19,7 +19,7 @@ internal class AssemblyFoldersFromConfigCache /// /// Set of files in ALL AssemblyFolderFromConfig directories /// - private readonly ImmutableHashSet _filesInDirectories = ImmutableHashSet.Empty; + private readonly HashSet _filesInDirectories; /// /// File exists delegate we are replacing @@ -45,12 +45,12 @@ internal AssemblyFoldersFromConfigCache(AssemblyFoldersFromConfig assemblyFolder } else { - _filesInDirectories = assemblyFoldersFromConfig.AsParallel() + _filesInDirectories = new(assemblyFoldersFromConfig.AsParallel() .Where(assemblyFolder => FileUtilities.DirectoryExistsNoThrow(assemblyFolder.DirectoryPath)) .SelectMany( assemblyFolder => - Directory.GetFiles(assemblyFolder.DirectoryPath, "*.*", SearchOption.TopDirectoryOnly)) - .ToImmutableHashSet(StringComparer.OrdinalIgnoreCase); + Directory.GetFiles(assemblyFolder.DirectoryPath, "*.*", SearchOption.TopDirectoryOnly)), + StringComparer.OrdinalIgnoreCase); } } diff --git a/src/Tasks/AssemblyDependency/AssemblyInformation.cs b/src/Tasks/AssemblyDependency/AssemblyInformation.cs index 531d95a8fc5..7f6fa93139c 100644 --- a/src/Tasks/AssemblyDependency/AssemblyInformation.cs +++ b/src/Tasks/AssemblyDependency/AssemblyInformation.cs @@ -50,7 +50,7 @@ internal class AssemblyInformation : DisposableBase #endif #if !FEATURE_ASSEMBLYLOADCONTEXT - private static string s_targetFrameworkAttribute = "System.Runtime.Versioning.TargetFrameworkAttribute"; + private const string s_targetFrameworkAttribute = "System.Runtime.Versioning.TargetFrameworkAttribute"; #endif #if !FEATURE_ASSEMBLYLOADCONTEXT // Borrowed from genman. diff --git a/src/Tasks/AssemblyDependency/GlobalAssemblyCache.cs b/src/Tasks/AssemblyDependency/GlobalAssemblyCache.cs index daeaae94b3b..1a6656c607d 100644 --- a/src/Tasks/AssemblyDependency/GlobalAssemblyCache.cs +++ b/src/Tasks/AssemblyDependency/GlobalAssemblyCache.cs @@ -283,7 +283,7 @@ internal static string GetLocation( bool useGacRarCache = Environment.GetEnvironmentVariable("MSBUILDDISABLEGACRARCACHE") == null; if (buildEngine != null && useGacRarCache) { - string key = "44d78b60-3bbe-48fe-9493-04119ebf515f" + "|" + targetProcessorArchitecture.ToString() + "|" + targetedRuntimeVersion.ToString() + "|" + fullFusionName.ToString() + "|" + specificVersion.ToString(); + string key = $"44d78b60-3bbe-48fe-9493-04119ebf515f|{targetProcessorArchitecture}|{targetedRuntimeVersion}|{fullFusionName}|{specificVersion}"; fusionNameToResolvedPath = buildEngine.GetRegisteredTaskObject(key, RegisteredTaskObjectLifetime.Build) as ConcurrentDictionary; if (fusionNameToResolvedPath == null) { diff --git a/src/Tasks/AssemblyDependency/Reference.cs b/src/Tasks/AssemblyDependency/Reference.cs index d9ba3671e32..78648beda64 100644 --- a/src/Tasks/AssemblyDependency/Reference.cs +++ b/src/Tasks/AssemblyDependency/Reference.cs @@ -725,7 +725,7 @@ internal HashSet RemappedAssemblyNames() /// internal void AddPreUnificationVersion(String referencePath, Version version, UnificationReason reason) { - string key = referencePath + version.ToString() + reason.ToString(); + string key = $"{referencePath}{version}{reason}"; // Only add a reference, version, and reason once. UnificationVersion unificationVersion; diff --git a/src/Tasks/AssemblyDependency/ReferenceTable.cs b/src/Tasks/AssemblyDependency/ReferenceTable.cs index de1a4b26b20..9e3b0c07d36 100644 --- a/src/Tasks/AssemblyDependency/ReferenceTable.cs +++ b/src/Tasks/AssemblyDependency/ReferenceTable.cs @@ -789,9 +789,7 @@ private static void TryGatherAssemblyNameEssentials(string fusionName, ref Assem return; } - string newFusionName = String.Format(CultureInfo.InvariantCulture, - "{0}, Version={1}, Culture={2}, PublicKeyToken={3}", - name, version, culture, publicKeyToken); + string newFusionName = $"{name}, Version={version}, Culture={culture}, PublicKeyToken={publicKeyToken}"; // Now try to convert to an AssemblyName. try @@ -812,19 +810,20 @@ private static void TryGatherAssemblyNameEssentials(string fusionName, ref Assem private static void TryGetAssemblyNameComponent(string fusionName, string component, ref string value) { int position = fusionName.IndexOf(component + "=", StringComparison.Ordinal); - if (position == -1) + if (position < 0) { return; } + position += component.Length + 1; - int nextDelimiter = fusionName.IndexOfAny([',', ' '], position); - if (nextDelimiter == -1) + int nextDelimiter = fusionName.AsSpan(position).IndexOfAny(',', ' '); + if (nextDelimiter < 0) { value = fusionName.Substring(position); } else { - value = fusionName.Substring(position, nextDelimiter - position); + value = fusionName.Substring(position, nextDelimiter); } } @@ -2301,20 +2300,11 @@ private static bool CompareRefToDef(AssemblyName @ref, AssemblyName def) byte[] rpkt = @ref.GetPublicKeyToken(); byte[] dpkt = def.GetPublicKeyToken(); - - if (rpkt.Length != dpkt.Length) + if (!rpkt.AsSpan().SequenceEqual(dpkt.AsSpan())) { return false; } - for (int i = 0; i < rpkt.Length; i++) - { - if (rpkt[i] != dpkt[i]) - { - return false; - } - } - if (@ref.Version != def.Version) { return false; diff --git a/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs b/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs index b322052e386..b331aeb86be 100644 --- a/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs +++ b/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs @@ -1311,6 +1311,9 @@ internal static string ByteArrayToString(byte[] a) return null; } +#if NET + return Convert.ToHexStringLower(a); +#else var buffer = new StringBuilder(a.Length * 2); for (int i = 0; i < a.Length; ++i) { @@ -1318,6 +1321,7 @@ internal static string ByteArrayToString(byte[] a) } return buffer.ToString(); +#endif } /// @@ -1536,22 +1540,22 @@ private void LogInputs() } Log.LogMessage(importance, property, "AppConfigFile"); - Log.LogMessage(importance, indent + AppConfigFile); + Log.LogMessage(importance, $"{indent}{AppConfigFile}"); Log.LogMessage(importance, property, "AutoUnify"); - Log.LogMessage(importance, indent + AutoUnify.ToString()); + Log.LogMessage(importance, $"{indent}{AutoUnify}"); Log.LogMessage(importance, property, "CopyLocalDependenciesWhenParentReferenceInGac"); - Log.LogMessage(importance, indent + _copyLocalDependenciesWhenParentReferenceInGac); + Log.LogMessage(importance, $"{indent}{_copyLocalDependenciesWhenParentReferenceInGac}"); Log.LogMessage(importance, property, "FindDependencies"); - Log.LogMessage(importance, indent + _findDependencies); + Log.LogMessage(importance, $"{indent}{_findDependencies}"); Log.LogMessage(importance, property, "TargetProcessorArchitecture"); - Log.LogMessage(importance, indent + TargetProcessorArchitecture); + Log.LogMessage(importance, $"{indent}{TargetProcessorArchitecture}"); Log.LogMessage(importance, property, "StateFile"); - Log.LogMessage(importance, indent + StateFile); + Log.LogMessage(importance, $"{indent}{StateFile}"); Log.LogMessage(importance, property, "InstalledAssemblySubsetTables"); foreach (ITaskItem installedAssemblySubsetTable in InstalledAssemblySubsetTables) @@ -1561,33 +1565,33 @@ private void LogInputs() } Log.LogMessage(importance, property, "IgnoreInstalledAssemblySubsetTable"); - Log.LogMessage(importance, indent + _ignoreDefaultInstalledAssemblySubsetTables); + Log.LogMessage(importance, $"{indent}{_ignoreDefaultInstalledAssemblySubsetTables}"); Log.LogMessage(importance, property, "TargetFrameworkSubsets"); foreach (string subset in _targetFrameworkSubsets) { - Log.LogMessage(importance, indent + subset); + Log.LogMessage(importance, $"{indent}{subset}"); } Log.LogMessage(importance, property, "FullTargetFrameworkSubsetNames"); foreach (string subset in FullTargetFrameworkSubsetNames) { - Log.LogMessage(importance, indent + subset); + Log.LogMessage(importance, $"{indent}{subset}"); } Log.LogMessage(importance, property, "ProfileName"); - Log.LogMessage(importance, indent + ProfileName); + Log.LogMessage(importance, $"{indent}{ProfileName}"); Log.LogMessage(importance, property, "FullFrameworkFolders"); foreach (string fullFolder in FullFrameworkFolders) { - Log.LogMessage(importance, indent + fullFolder); + Log.LogMessage(importance, $"{indent}{fullFolder}"); } Log.LogMessage(importance, property, "LatestTargetFrameworkDirectories"); foreach (string latestFolder in _latestTargetFrameworkDirectories) { - Log.LogMessage(importance, indent + latestFolder); + Log.LogMessage(importance, $"{indent}{latestFolder}"); } Log.LogMessage(importance, property, "ProfileTablesLocation"); @@ -2041,7 +2045,7 @@ private void LogConflict(Reference reference, string fusionName, StringBuilder l break; } } - #endregion +#endregion #region StateFile /// @@ -2843,7 +2847,7 @@ internal static string GenerateSubSetName(string[] frameworkSubSetNames, ITaskIt } } - return String.Join(", ", subsetNames.ToArray()); + return String.Join(", ", subsetNames); } /// diff --git a/src/Tasks/BootstrapperUtil/BootstrapperBuilder.cs b/src/Tasks/BootstrapperUtil/BootstrapperBuilder.cs index c22ab73bc9f..8e7c85a73b3 100644 --- a/src/Tasks/BootstrapperUtil/BootstrapperBuilder.cs +++ b/src/Tasks/BootstrapperUtil/BootstrapperBuilder.cs @@ -492,9 +492,9 @@ internal string[] Cultures Refresh(); } - List list = _cultures.Values.Select(v => v.ToString()).ToList(); - list.Sort(); - return list.ToArray(); + string[] array = _cultures.Values.Select(v => v.ToString()).ToArray(); + Array.Sort(array); + return array; } } @@ -603,7 +603,7 @@ private void RefreshProducts() foreach (string strSubDirectory in Directory.GetDirectories(packagePath)) { int nStartIndex = packagePath.Length; - if ((strSubDirectory.ToCharArray())[nStartIndex] == System.IO.Path.DirectorySeparatorChar) + if (strSubDirectory[nStartIndex] == System.IO.Path.DirectorySeparatorChar) { nStartIndex++; } @@ -948,7 +948,7 @@ private void ExploreDirectory(string strSubDirectory, XmlElement rootElement, st } XmlNode langNode = langDoc.SelectSingleNode(BOOTSTRAPPER_PREFIX + ":Package", _xmlNamespaceManager); - Debug.Assert(langNode != null, string.Format(CultureInfo.CurrentCulture, "Unable to find a package node in {0}", strLangManifestFilename)); + Debug.Assert(langNode != null, $"Unable to find a package node in {strLangManifestFilename}"); if (langNode != null) { XmlElement langElement = (XmlElement)(_document.ImportNode(langNode, true)); @@ -1040,7 +1040,7 @@ private void ExploreDirectory(string strSubDirectory, XmlElement rootElement, st } else { - Debug.WriteLine(String.Format(CultureInfo.CurrentCulture, "Validation results already added for Product Code '{0}'", productCodeAttribute)); + Debug.WriteLine($"Validation results already added for Product Code '{productCodeAttribute}'"); } } } @@ -1512,7 +1512,7 @@ private bool BuildPackages(BuildSettings settings, XmlElement configElement, Res // Add the file size to the PackageFileNode XmlAttribute sizeAttribute = packageFileNode.OwnerDocument.CreateAttribute("Size"); var fi = new FileInfo(packageFileSource.Value); - sizeAttribute.Value = "" + (fi.Length.ToString(CultureInfo.InvariantCulture)); + sizeAttribute.Value = fi.Length.ToString(CultureInfo.InvariantCulture); MergeAttribute(packageFileNode, sizeAttribute); } } @@ -1547,7 +1547,7 @@ private bool BuildPackages(BuildSettings settings, XmlElement configElement, Res if (configElement != null) { configElement.AppendChild(configElement.OwnerDocument.ImportNode(node, true)); - DumpXmlToFile(node, string.Format(CultureInfo.CurrentCulture, "{0}.{1}.xml", package.Product.ProductCode, package.Culture)); + DumpXmlToFile(node, $"{package.Product.ProductCode}.{package.Culture}.xml"); } } @@ -1602,13 +1602,17 @@ private static string ByteArrayToString(byte[] byteArray) return null; } - var output = new StringBuilder(byteArray.Length); +#if NET + return Convert.ToHexString(byteArray); +#else + var output = new StringBuilder(byteArray.Length * 2); foreach (byte byteValue in byteArray) { output.Append(byteValue.ToString("X02", CultureInfo.InvariantCulture)); } return output.ToString(); +#endif } private static string GetFileHash(string filePath) @@ -1983,7 +1987,7 @@ private static Stream GetEmbeddedResourceStream(string name) { Assembly a = Assembly.GetExecutingAssembly(); Stream s = a.GetManifestResourceStream(String.Format(CultureInfo.InvariantCulture, "{0}.{1}", typeof(BootstrapperBuilder).Namespace, name)); - Debug.Assert(s != null, String.Format(CultureInfo.CurrentCulture, "EmbeddedResource '{0}' not found", name)); + Debug.Assert(s != null, $"EmbeddedResource '{name}' not found"); return s; } diff --git a/src/Tasks/BootstrapperUtil/BuildMessage.cs b/src/Tasks/BootstrapperUtil/BuildMessage.cs index bc4272ed8d8..db219a6a7d0 100644 --- a/src/Tasks/BootstrapperUtil/BuildMessage.cs +++ b/src/Tasks/BootstrapperUtil/BuildMessage.cs @@ -13,9 +13,14 @@ namespace Microsoft.Build.Tasks.Deployment.Bootstrapper /// /// Represents messages that occur during the BootstrapperBuilder's Build operation. /// - public class BuildMessage : IBuildMessage + public partial class BuildMessage : IBuildMessage { - private static readonly Regex s_msbuildMessageCodePattern = new Regex(@"(\d+)$"); +#if NET + [GeneratedRegex(@"\d+$")] + private static partial Regex MsbuildMessageCodePattern { get; } +#else + private static Regex MsbuildMessageCodePattern { get; } = new Regex(@"\d+$"); +#endif private BuildMessage(BuildMessageSeverity severity, string message, string helpKeyword, string helpCode) { @@ -25,7 +30,7 @@ private BuildMessage(BuildMessageSeverity severity, string message, string helpK HelpCode = helpCode; if (!String.IsNullOrEmpty(HelpCode)) { - Match match = s_msbuildMessageCodePattern.Match(HelpCode); + Match match = MsbuildMessageCodePattern.Match(HelpCode); if (match.Success) { HelpId = int.Parse(match.Value, CultureInfo.InvariantCulture); diff --git a/src/Tasks/BootstrapperUtil/Product.cs b/src/Tasks/BootstrapperUtil/Product.cs index 6e9d0e1ceca..bf788c343b5 100644 --- a/src/Tasks/BootstrapperUtil/Product.cs +++ b/src/Tasks/BootstrapperUtil/Product.cs @@ -168,7 +168,7 @@ internal void AddPackage(Package package) } else { - Debug.WriteLine(String.Format(CultureInfo.CurrentCulture, "A package with culture '{0}' has already been added to product '{1}'", package.Culture.ToLowerInvariant(), ProductCode)); + Debug.WriteLine($"A package with culture '{package.Culture.ToLowerInvariant()}' has already been added to product '{ProductCode}'"); } } diff --git a/src/Tasks/BootstrapperUtil/Util.cs b/src/Tasks/BootstrapperUtil/Util.cs index 973ca74a61f..bfd845b297f 100644 --- a/src/Tasks/BootstrapperUtil/Util.cs +++ b/src/Tasks/BootstrapperUtil/Util.cs @@ -124,7 +124,12 @@ public static string GetDefaultPath(string visualStudioVersion) { dotIndex = visualStudioVersion.Length; } + +#if NET + if (Int32.TryParse(visualStudioVersion.AsSpan(0, dotIndex), out int majorVersion) && (majorVersion < 11)) +#else if (Int32.TryParse(visualStudioVersion.Substring(0, dotIndex), out int majorVersion) && (majorVersion < 11)) +#endif { visualStudioVersion = BOOTSTRAPPER_REGISTRY_PATH_VERSION_VS2010; } diff --git a/src/Tasks/ComReference.cs b/src/Tasks/ComReference.cs index 340be0e84b1..67e50ba29d6 100644 --- a/src/Tasks/ComReference.cs +++ b/src/Tasks/ComReference.cs @@ -263,7 +263,7 @@ internal static bool GetTypeLibNameForITypeLib(TaskLoggingHelper log, bool silen if (typeLibName.Length >= 4) { - if (string.Equals(typeLibName.Substring(typeLibName.Length - 4), ".dll", StringComparison.OrdinalIgnoreCase)) + if (typeLibName.AsSpan().EndsWith(".dll".AsSpan(), StringComparison.OrdinalIgnoreCase)) { typeLibName = typeLibName.Substring(0, typeLibName.Length - 4); } diff --git a/src/Tasks/CultureInfoCache.cs b/src/Tasks/CultureInfoCache.cs index aed8b824d4f..53448eb1ba3 100644 --- a/src/Tasks/CultureInfoCache.cs +++ b/src/Tasks/CultureInfoCache.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; using System.Globalization; -#if NET5_0_OR_GREATER +#if NET using System.Linq; using Microsoft.Build.Framework; #endif @@ -23,7 +23,7 @@ namespace Microsoft.Build.Tasks /// internal static class CultureInfoCache { -#if !NET5_0_OR_GREATER +#if !NET private static readonly Lazy> ValidCultureNames = new Lazy>(() => InitializeValidCultureNames()); #endif @@ -63,7 +63,7 @@ private static HashSet InitializeValidCultureNames() /// True if the culture is determined to be valid. internal static bool IsValidCultureString(string name) { -#if NET5_0_OR_GREATER +#if NET try { // GetCultureInfo throws if the culture doesn't exist diff --git a/src/Tasks/DownloadFile.cs b/src/Tasks/DownloadFile.cs index 71dc72e4c91..9028bd1f386 100644 --- a/src/Tasks/DownloadFile.cs +++ b/src/Tasks/DownloadFile.cs @@ -156,7 +156,7 @@ private async Task DownloadAsync(Uri uri, CancellationToken cancellationToken) { response.EnsureSuccessStatusCode(); } -#if NET6_0_OR_GREATER +#if NET catch (HttpRequestException) { throw; @@ -203,7 +203,7 @@ private async Task DownloadAsync(Uri uri, CancellationToken cancellationToken) Log.LogMessageFromResources(MessageImportance.High, "DownloadFile.Downloading", SourceUrl, destinationFile.FullName, response.Content.Headers.ContentLength); #pragma warning disable SA1111, SA1009 // Closing parenthesis should be on line of last parameter using (Stream responseStream = await response.Content.ReadAsStreamAsync( -#if NET6_0_OR_GREATER +#if NET cancellationToken #endif ).ConfigureAwait(false)) @@ -260,7 +260,7 @@ private static bool IsRetriable(Exception exception, out Exception actualExcepti } } -#if NET6_0_OR_GREATER +#if NET // net5.0 included StatusCode in the HttpRequestException. switch (httpRequestException.StatusCode) { @@ -329,7 +329,7 @@ private bool TryGetFileName(HttpResponseMessage response, out string filename) return !String.IsNullOrWhiteSpace(filename); } -#if !NET6_0_OR_GREATER +#if !NET /// /// Represents a wrapper around the that also contains the . /// DEPRECATED as of net5.0, which included the StatusCode in the HttpRequestException class. diff --git a/src/Tasks/FileIO/GetFileHash.cs b/src/Tasks/FileIO/GetFileHash.cs index 2d1ec4f5308..b975be5fd5e 100644 --- a/src/Tasks/FileIO/GetFileHash.cs +++ b/src/Tasks/FileIO/GetFileHash.cs @@ -143,7 +143,7 @@ internal static byte[] ComputeHash(Func algorithmFactory, string using (var stream = File.OpenRead(filePath)) using (var algorithm = algorithmFactory()) { -#if NET5_0_OR_GREATER +#if NET return algorithm.ComputeHashAsync(stream, ct).Result; #else return algorithm.ComputeHash(stream); diff --git a/src/Tasks/FindInvalidProjectReferences.cs b/src/Tasks/FindInvalidProjectReferences.cs index 791ac147af6..607df8b83cd 100644 --- a/src/Tasks/FindInvalidProjectReferences.cs +++ b/src/Tasks/FindInvalidProjectReferences.cs @@ -13,17 +13,22 @@ namespace Microsoft.Build.Tasks /// /// Returns the reference assembly paths to the various frameworks /// - public class FindInvalidProjectReferences : TaskExtension + public partial class FindInvalidProjectReferences : TaskExtension { #region Fields + private const string PlatformMonikerFormatPattern = @"(?^[^,]*),\s*Version=(?.*)"; + /// /// Regex for breaking up the platform moniker /// Example: XNA, Version=8.0 /// - private static readonly Regex s_platformMonikerFormat = new Regex( - @"(?^[^,]*),\s*Version=(?.*)", - RegexOptions.IgnoreCase); +#if NET + [GeneratedRegex(PlatformMonikerFormatPattern, RegexOptions.IgnoreCase)] + private static partial Regex PlatformMonikerRegex { get; } +#else + private static Regex PlatformMonikerRegex { get; } = new Regex(PlatformMonikerFormatPattern, RegexOptions.IgnoreCase); +#endif /// /// Reference moniker metadata @@ -111,7 +116,7 @@ public override bool Execute() /// private static bool ParseMoniker(string reference, out string platformIdentity, out Version platformVersion) { - Match match = s_platformMonikerFormat.Match(reference); + Match match = PlatformMonikerRegex.Match(reference); platformIdentity = String.Empty; bool parsedVersion = false; diff --git a/src/Tasks/FormatVersion.cs b/src/Tasks/FormatVersion.cs index 88cfc595cb5..86182dca239 100644 --- a/src/Tasks/FormatVersion.cs +++ b/src/Tasks/FormatVersion.cs @@ -47,9 +47,14 @@ public override bool Execute() { OutputVersion = "1.0.0.0"; } - else if (Version.EndsWith("*", StringComparison.Ordinal)) + else if (Version[Version.Length - 1] == '*') { - OutputVersion = Version.Substring(0, Version.Length - 1) + Revision.ToString("G", CultureInfo.InvariantCulture); + OutputVersion = +#if NET + string.Create(CultureInfo.InvariantCulture, $"{Version.AsSpan(0, Version.Length - 1)}{Revision:G}"); +#else + Version.Substring(0, Version.Length - 1) + Revision.ToString("G", CultureInfo.InvariantCulture); +#endif } else { diff --git a/src/Tasks/GenerateApplicationManifest.cs b/src/Tasks/GenerateApplicationManifest.cs index 1c32b6feb36..c91044032b0 100644 --- a/src/Tasks/GenerateApplicationManifest.cs +++ b/src/Tasks/GenerateApplicationManifest.cs @@ -241,7 +241,7 @@ private bool AddIsolatedComReferences(ApplicationManifest manifest) } } - Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "GenerateApplicationManifest.AddIsolatedComReferences t={0}", Environment.TickCount - t1)); + Util.WriteLog($"GenerateApplicationManifest.AddIsolatedComReferences t={Environment.TickCount - t1}"); return success; } @@ -326,7 +326,7 @@ private bool AddClickOnceFiles(ApplicationManifest manifest) } } - Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "GenerateApplicationManifest.AddClickOnceFiles t={0}", Environment.TickCount - t1)); + Util.WriteLog($"GenerateApplicationManifest.AddClickOnceFiles t={Environment.TickCount - t1}"); return true; } diff --git a/src/Tasks/GenerateManifestBase.cs b/src/Tasks/GenerateManifestBase.cs index a58209b9053..96ae300d52a 100644 --- a/src/Tasks/GenerateManifestBase.cs +++ b/src/Tasks/GenerateManifestBase.cs @@ -519,7 +519,7 @@ private bool ResolveFiles() return false; } - Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "GenerateManifestBase.ResolveFiles t={0}", Environment.TickCount - t1)); + Util.WriteLog($"GenerateManifestBase.ResolveFiles t={Environment.TickCount - t1}"); return true; } @@ -625,8 +625,8 @@ private bool WriteManifest() return false; } - Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "GenerateManifestBase.WriteManifest t={0}", Environment.TickCount - t1)); - Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "Total time to generate manifest '{1}': t={0}", Environment.TickCount - _startTime, Path.GetFileName(OutputManifest.ItemSpec))); + Util.WriteLog($"GenerateManifestBase.WriteManifest t={Environment.TickCount - t1}"); + Util.WriteLog($"Total time to generate manifest '{Path.GetFileName(OutputManifest.ItemSpec)}': t={Environment.TickCount - _startTime}"); return true; } } diff --git a/src/Tasks/GenerateResource.cs b/src/Tasks/GenerateResource.cs index b888da26a59..b7e981bea5f 100644 --- a/src/Tasks/GenerateResource.cs +++ b/src/Tasks/GenerateResource.cs @@ -145,7 +145,7 @@ public sealed partial class GenerateResource : TaskExtension, IIncrementalTask #if FEATURE_RESGEN // Our calculation is not quite correct. Using a number substantially less than 32768 in order to // be sure we don't exceed it. - private static int s_maximumCommandLength = 28000; + private const int s_maximumCommandLength = 28000; #endif // FEATURE_RESGEN // Contains the list of paths from which inputs will not be taken into account during up-to-date check. @@ -1793,7 +1793,7 @@ private bool NeedSeparateAppDomain() string resolvedTypeName = typeName; // This type name might be an alias, so first resolve that if any. - int indexOfSeperator = typeName.IndexOf(",", StringComparison.Ordinal); + int indexOfSeperator = typeName.IndexOf(','); if (indexOfSeperator != -1) { @@ -2017,18 +2017,13 @@ private bool DetermineWhetherSerializedObjectLoads(string data) } #endif - /// - /// Chars that should be ignored in the nicely justified block of base64 - /// - private static readonly char[] s_specialChars = [' ', '\r', '\n']; - /// /// Turns the nicely justified block of base64 found in a resx into a byte array. /// Copied from fx\src\winforms\managed\system\winforms\control.cs /// private static byte[] ByteArrayFromBase64WrappedString(string text) { - if (text.IndexOfAny(s_specialChars) != -1) + if (text.AsSpan().IndexOfAny(' ', '\r', '\n') != -1) // Chars that should be ignored in the nicely justified block of base64 { StringBuilder sb = new StringBuilder(text.Length); for (int i = 0; i < text.Length; i++) @@ -3044,7 +3039,7 @@ private void ReadResources(String filename, bool shouldUseSourcePath, String out default: // We should never get here, we've already checked the format - Debug.Fail("Unknown format " + format.ToString()); + Debug.Fail($"Unknown format {format}"); return; } _logger.LogMessageFromResources(MessageImportance.Low, "GenerateResource.ReadResourceMessage", reader.resources.Count, filename); @@ -3325,7 +3320,7 @@ private void WriteResources(ReaderInfo reader, String filename) #if FEATURE_RESXREADER_LIVEDESERIALIZATION WriteResources(reader, new ResXResourceWriter(filename)); // closes writer for us #else - _logger.LogError(format.ToString() + " not supported on .NET Core MSBuild"); + _logger.LogError($"{format} not supported on .NET Core MSBuild"); #endif break; @@ -3339,7 +3334,7 @@ private void WriteResources(ReaderInfo reader, String filename) default: // We should never get here, we've already checked the format - Debug.Fail("Unknown format " + format.ToString()); + Debug.Fail($"Unknown format {format}"); break; } } @@ -3695,7 +3690,13 @@ private void ReadTextResources(ReaderInfo reader, String fileName) } try { - ch = (char)UInt16.Parse(new String(hex), NumberStyles.HexNumber, CultureInfo.CurrentCulture); + ch = (char)UInt16.Parse( +#if NET + hex, +#else + new String(hex), +#endif + NumberStyles.HexNumber, CultureInfo.CurrentCulture); } catch (FormatException) { @@ -4013,7 +4014,7 @@ internal int LinePosition get { return column; } } } - #endregion // Code from ResGen.EXE +#endregion // Code from ResGen.EXE } #if !FEATURE_ASSEMBLYLOADCONTEXT @@ -4150,7 +4151,7 @@ public Type GetType(string name, bool throwOnError, bool ignoreCase) result = a.GetType(name, false, ignoreCase); if (result == null) { - int indexOfComma = name.IndexOf(",", StringComparison.Ordinal); + int indexOfComma = name.IndexOf(','); if (indexOfComma != -1) { string shortName = name.Substring(0, indexOfComma); diff --git a/src/Tasks/GetAssemblyIdentity.cs b/src/Tasks/GetAssemblyIdentity.cs index 1d5c78c929e..f818366bc9f 100644 --- a/src/Tasks/GetAssemblyIdentity.cs +++ b/src/Tasks/GetAssemblyIdentity.cs @@ -45,12 +45,17 @@ private static string ByteArrayToHex(Byte[] a) { return null; } + +#if NET + return Convert.ToHexString(a); +#else var s = new StringBuilder(a.Length * 2); foreach (Byte b in a) { s.Append(b.ToString("X02", CultureInfo.InvariantCulture)); } return s.ToString(); +#endif } public override bool Execute() diff --git a/src/Tasks/GetSDKReferenceFiles.cs b/src/Tasks/GetSDKReferenceFiles.cs index b1a24a00d6a..34f84047a3f 100644 --- a/src/Tasks/GetSDKReferenceFiles.cs +++ b/src/Tasks/GetSDKReferenceFiles.cs @@ -1090,7 +1090,7 @@ internal bool IsAssemblyListCacheFileUpToDate(string sdkIdentity, string sdkRoot string currentAssembly = String.Empty; try { -#if NETCOREAPP +#if NET currentAssembly = Assembly.GetExecutingAssembly().Location; #else currentAssembly = Assembly.GetExecutingAssembly().CodeBase; diff --git a/src/Tasks/Hash.cs b/src/Tasks/Hash.cs index 0bc42f56bfe..828012a2add 100644 --- a/src/Tasks/Hash.cs +++ b/src/Tasks/Hash.cs @@ -94,6 +94,9 @@ public override bool Execute() sha.TransformFinalBlock(shaBuffer, 0, shaBufferPosition); +#if NET + HashResult = Convert.ToHexStringLower(sha.Hash); +#else using (var stringBuilder = new ReuseableStringBuilder(sha.HashSize)) { foreach (var b in sha.Hash) @@ -102,6 +105,7 @@ public override bool Execute() } HashResult = stringBuilder.ToString(); } +#endif } finally { diff --git a/src/Tasks/MSBuild.cs b/src/Tasks/MSBuild.cs index 3169eaec219..12426e0c9aa 100644 --- a/src/Tasks/MSBuild.cs +++ b/src/Tasks/MSBuild.cs @@ -261,10 +261,7 @@ public override bool Execute() if (BuildInParallel) { skipProjects = new bool[Projects.Length]; - for (int i = 0; i < skipProjects.Length; i++) - { - skipProjects[i] = true; - } + skipProjects.AsSpan().Fill(true); } else { diff --git a/src/Tasks/ManifestUtil/ApplicationManifest.cs b/src/Tasks/ManifestUtil/ApplicationManifest.cs index 081762e8b84..555734fde12 100644 --- a/src/Tasks/ManifestUtil/ApplicationManifest.cs +++ b/src/Tasks/ManifestUtil/ApplicationManifest.cs @@ -508,7 +508,7 @@ private void ValidateCom() } } - Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "GenerateManifest.CheckForComDuplicates t={0}", Environment.TickCount - t1)); + Util.WriteLog($"GenerateManifest.CheckForComDuplicates t={Environment.TickCount - t1}"); } private void ValidateConfig() @@ -661,11 +661,11 @@ private void ValidateReferencesForClickOnceApplication() // Check for two or more items with the same TargetPath... string key = assembly.TargetPath.ToLowerInvariant(); - if (!targetPathList.ContainsKey(key)) + if (!targetPathList.TryGetValue(key, out bool value)) { targetPathList.Add(key, false); } - else if (!targetPathList[key]) + else if (!value) { OutputMessages.AddWarningMessage("GenerateManifest.DuplicateTargetPath", assembly.ToString()); targetPathList[key] = true; // only warn once per path @@ -707,18 +707,18 @@ private void ValidateReferencesForClickOnceApplication() // Check for two or more items with the same TargetPath... string key = file.TargetPath.ToLowerInvariant(); - if (!targetPathList.ContainsKey(key)) + if (!targetPathList.TryGetValue(key, out bool value)) { targetPathList.Add(key, false); } - else if (!targetPathList[key]) + else if (!value) { OutputMessages.AddWarningMessage("GenerateManifest.DuplicateTargetPath", file.TargetPath); targetPathList[key] = true; // only warn once per path } } } - Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "GenerateManifest.CheckManifestReferences t={0}", Environment.TickCount - t1)); + Util.WriteLog($"GenerateManifest.CheckManifestReferences t={Environment.TickCount - t1}"); } private void ValidateReferenceForPartialTrust(AssemblyReference assembly, TrustInfo trustInfo) diff --git a/src/Tasks/ManifestUtil/AssemblyIdentity.cs b/src/Tasks/ManifestUtil/AssemblyIdentity.cs index 6328476020f..28dbbfef17f 100644 --- a/src/Tasks/ManifestUtil/AssemblyIdentity.cs +++ b/src/Tasks/ManifestUtil/AssemblyIdentity.cs @@ -26,7 +26,7 @@ namespace Microsoft.Build.Tasks.Deployment.ManifestUtilities /// This is a serialization format, do not remove or change the private fields. [ComVisible(false)] [XmlRoot("AssemblyIdentity")] - public sealed class AssemblyIdentity + public sealed partial class AssemblyIdentity { /// /// Specifies which attributes are to be returned by the GetFullName function. @@ -59,6 +59,17 @@ public enum FullNameFlags private string _processorArchitecture; private string _type; + private const string AssemblyNamePattern = + "^(?[^,]*)(, Version=(?[^,]*))?(, Culture=(?[^,]*))?(, PublicKeyToken=(?[^,]*))?(, ProcessorArchitecture=(?[^,]*))?(, Type=(?[^,]*))?"; + +#if NET + [GeneratedRegex(AssemblyNamePattern)] + private static partial Regex AssemblyNameRegex { get; } +#else + private static Regex AssemblyNameRegex => _assemblyNameRegex ??= new Regex(AssemblyNamePattern); + private static Regex _assemblyNameRegex; +#endif + /// /// Initializes a new instance of the AssemblyIdentity class. /// @@ -165,7 +176,7 @@ public AssemblyIdentity(AssemblyIdentity identity) public static AssemblyIdentity FromAssemblyName(string assemblyName) { // NOTE: We're not using System.Reflection.AssemblyName class here because we need ProcessorArchitecture and Type attributes. - Regex re = new Regex("^(?[^,]*)(, Version=(?[^,]*))?(, Culture=(?[^,]*))?(, PublicKeyToken=(?[^,]*))?(, ProcessorArchitecture=(?[^,]*))?(, Type=(?[^,]*))?"); + Regex re = AssemblyNameRegex; Match m = re.Match(assemblyName); string name = m.Result("${name}"); string version = m.Result("${version}"); @@ -367,10 +378,15 @@ public bool IsInFramework(string frameworkIdentifier, string frameworkVersion) Version version = null; if (!string.IsNullOrEmpty(frameworkVersion)) { - // CA1307:Specify StringComparison. Suppressed since a valid string representation of a version would be parsed correctly even if the the first character is not "v". - if (frameworkVersion.StartsWith("v")) + if (frameworkVersion[0] == 'v') { - System.Version.TryParse(frameworkVersion.Substring(1), out version); + System.Version.TryParse( +#if NET + frameworkVersion.AsSpan(1), +#else + frameworkVersion.Substring(1), +#endif + out version); } else { @@ -514,14 +530,14 @@ internal string Resolve(string[] searchPaths, bool specificVersion) foreach (string searchPath in searchPaths) { - string file = String.Format(CultureInfo.InvariantCulture, "{0}.dll", _name); + string file = $"{_name}.dll"; string path = Path.Combine(searchPath, file); if (FileSystems.Default.FileExists(path) && IsEqual(this, FromFile(path), specificVersion)) { return path; } - file = String.Format(CultureInfo.InvariantCulture, "{0}.manifest", _name); + file = $"{_name}.manifest"; path = Path.Combine(searchPath, file); if (FileSystems.Default.FileExists(path) && IsEqual(this, FromManifest(path), specificVersion)) { diff --git a/src/Tasks/ManifestUtil/ConvertUtil.cs b/src/Tasks/ManifestUtil/ConvertUtil.cs index 7945fed64e5..88be5e23f8a 100644 --- a/src/Tasks/ManifestUtil/ConvertUtil.cs +++ b/src/Tasks/ManifestUtil/ConvertUtil.cs @@ -26,11 +26,11 @@ public static bool ToBoolean(string value, bool defaultValue) } catch (FormatException) { - Debug.Fail(String.Format(CultureInfo.CurrentCulture, "Invalid value '{0}' for {1}, returning {2}", value, typeof(bool).Name, defaultValue.ToString())); + Debug.Fail($"Invalid value '{value}' for {typeof(bool).Name}, returning {defaultValue}"); } catch (ArgumentException) { - Debug.Fail(String.Format(CultureInfo.CurrentCulture, "Invalid value '{0}' for {1}, returning {2}", value, typeof(bool).Name, defaultValue.ToString())); + Debug.Fail($"Invalid value '{value}' for {typeof(bool).Name}, returning {defaultValue}"); } } return defaultValue; diff --git a/src/Tasks/ManifestUtil/EmbeddedManifestReader.cs b/src/Tasks/ManifestUtil/EmbeddedManifestReader.cs index 01cb5f8f5d3..ff62bb0b948 100644 --- a/src/Tasks/ManifestUtil/EmbeddedManifestReader.cs +++ b/src/Tasks/ManifestUtil/EmbeddedManifestReader.cs @@ -74,7 +74,7 @@ public static Stream Read(string path) int t1 = Environment.TickCount; EmbeddedManifestReader r = new EmbeddedManifestReader(path); - Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "EmbeddedManifestReader.Read t={0}", Environment.TickCount - t1)); + Util.WriteLog($"EmbeddedManifestReader.Read t={Environment.TickCount - t1}"); return r._manifest; } } diff --git a/src/Tasks/ManifestUtil/Manifest.cs b/src/Tasks/ManifestUtil/Manifest.cs index d8e5b2a8fdd..63b307cd6df 100644 --- a/src/Tasks/ManifestUtil/Manifest.cs +++ b/src/Tasks/ManifestUtil/Manifest.cs @@ -630,11 +630,11 @@ private void ValidateReferences() // Check for two or more assemblies with the same identity... string identity = assembly.AssemblyIdentity.GetFullName(AssemblyIdentity.FullNameFlags.All); string key = identity.ToLowerInvariant(); - if (!identityList.ContainsKey(key)) + if (!identityList.TryGetValue(key, out bool value)) { identityList.Add(key, false); } - else if (!identityList[key]) + else if (!value) { OutputMessages.AddWarningMessage("GenerateManifest.DuplicateAssemblyIdentity", identity); identityList[key] = true; // only warn once per identity diff --git a/src/Tasks/ManifestUtil/ManifestFormatter.cs b/src/Tasks/ManifestUtil/ManifestFormatter.cs index d7d7ee01eb1..91405ecaac7 100644 --- a/src/Tasks/ManifestUtil/ManifestFormatter.cs +++ b/src/Tasks/ManifestUtil/ManifestFormatter.cs @@ -97,8 +97,8 @@ public static Stream Format(Stream input) w.WriteEndDocument(); w.Flush(); m.Position = 0; - Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "ManifestWriter.Format t={0}", Environment.TickCount - t1)); + Util.WriteLog($"ManifestWriter.Format t={Environment.TickCount - t1}"); return m; } } -} \ No newline at end of file +} diff --git a/src/Tasks/ManifestUtil/ManifestReader.cs b/src/Tasks/ManifestUtil/ManifestReader.cs index 131de566c33..1bd48abe2db 100644 --- a/src/Tasks/ManifestUtil/ManifestReader.cs +++ b/src/Tasks/ManifestUtil/ManifestReader.cs @@ -246,7 +246,7 @@ private static Manifest Deserialize(Stream s) using (XmlReader xr = XmlReader.Create(s, xrSettings)) { var m = (Manifest)xs.Deserialize(xr); - Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "ManifestReader.Deserialize t={0}", Environment.TickCount - t1)); + Util.WriteLog($"ManifestReader.Deserialize t={Environment.TickCount - t1}"); return m; } } diff --git a/src/Tasks/ManifestUtil/ManifestWriter.cs b/src/Tasks/ManifestUtil/ManifestWriter.cs index 8da08fbacde..c5aff0aeae1 100644 --- a/src/Tasks/ManifestUtil/ManifestWriter.cs +++ b/src/Tasks/ManifestUtil/ManifestWriter.cs @@ -28,7 +28,7 @@ private static Stream Serialize(Manifest manifest) int t1 = Environment.TickCount; s.Serialize(w, manifest); - Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "ManifestWriter.Serialize t={0}", Environment.TickCount - t1)); + Util.WriteLog($"ManifestWriter.Serialize t={Environment.TickCount - t1}"); w.Flush(); m.Position = 0; @@ -188,7 +188,7 @@ private static void WriteManifest(Manifest manifest, Stream output, string targe Util.WriteLogFile(n + ".write.3-formatted.xml", s4); Util.CopyStream(s4, output); - Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "ManifestWriter.WriteManifest t={0}", Environment.TickCount - t1)); + Util.WriteLog($"ManifestWriter.WriteManifest t={Environment.TickCount - t1}"); } } } diff --git a/src/Tasks/ManifestUtil/MetadataReader.cs b/src/Tasks/ManifestUtil/MetadataReader.cs index fe8269ecdca..efd6d271087 100644 --- a/src/Tasks/ManifestUtil/MetadataReader.cs +++ b/src/Tasks/ManifestUtil/MetadataReader.cs @@ -173,7 +173,12 @@ private string GetPublicKeyToken() an.SetPublicKey(pk); byte[] pkt = an.GetPublicKeyToken(); - publicKeyToken = BitConverter.ToString(pkt).Replace("-", ""); + publicKeyToken = +#if NET + Convert.ToHexString(pkt); +#else + BitConverter.ToString(pkt).Replace("-", ""); +#endif } if (!String.IsNullOrEmpty(publicKeyToken)) diff --git a/src/Tasks/ManifestUtil/PathUtil.cs b/src/Tasks/ManifestUtil/PathUtil.cs index 81c678e17cf..705f2a79d3b 100644 --- a/src/Tasks/ManifestUtil/PathUtil.cs +++ b/src/Tasks/ManifestUtil/PathUtil.cs @@ -4,6 +4,7 @@ using System; using System.IO; using System.Linq; +using System.Text; using Microsoft.Build.Shared; #nullable disable @@ -222,7 +223,13 @@ public static string Resolve(string path) { // Unfortunately Uri.Host is read-only, so we need to reconstruct it manually... int i = path.IndexOf(localHost, StringComparison.OrdinalIgnoreCase); - return i >= 0 ? path.Substring(0, i) + Environment.MachineName.ToLowerInvariant() + path.Substring(i + localHost.Length) : path; + return i >= 0 ? +#if NET + $"{path.AsSpan(0, i)}{Environment.MachineName.ToLowerInvariant()}{path.AsSpan(i + localHost.Length)}" : +#else + $"{path.Substring(0, i)}{Environment.MachineName.ToLowerInvariant()}{path.Substring(i + localHost.Length)}" : +#endif + path; } return path; } @@ -231,7 +238,11 @@ public static string Resolve(string path) return Path.GetFullPath(path); // make sure it's a full path } - private static bool IsAsciiString(string str) - => str.All(c => c <= 127); + private static bool IsAsciiString(string str) => +#if NET + Ascii.IsValid(str); +#else + str.All(c => c <= 127); +#endif } } diff --git a/src/Tasks/ManifestUtil/TrustInfo.cs b/src/Tasks/ManifestUtil/TrustInfo.cs index bc10cb1d02c..07eaa9e03d2 100644 --- a/src/Tasks/ManifestUtil/TrustInfo.cs +++ b/src/Tasks/ManifestUtil/TrustInfo.cs @@ -790,7 +790,7 @@ public void WriteManifest(Stream input, Stream output) output.Flush(); } document.Save(output); - Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "ManifestWriter.WriteTrustInfo t={0}", Environment.TickCount - t1)); + Util.WriteLog($"ManifestWriter.WriteTrustInfo t={Environment.TickCount - t1}"); } } } diff --git a/src/Tasks/ManifestUtil/Util.cs b/src/Tasks/ManifestUtil/Util.cs index a6b50bd028f..a2d89b437c7 100644 --- a/src/Tasks/ManifestUtil/Util.cs +++ b/src/Tasks/ManifestUtil/Util.cs @@ -67,47 +67,25 @@ public static string ByteArrayToHex(Byte[] a) return null; } - StringBuilder s = new StringBuilder(a.Length); +#if NET + return Convert.ToHexString(a); +#else + StringBuilder s = new StringBuilder(a.Length * 2); foreach (Byte b in a) { s.Append(b.ToString("X02", CultureInfo.InvariantCulture)); } return s.ToString(); +#endif } - public static string ByteArrayToString(Byte[] a) - { - if (a == null) - { - return null; - } - - StringBuilder s = new StringBuilder(a.Length); - foreach (Byte b in a) - { - s.Append(Convert.ToChar(b)); - } - - return s.ToString(); - } - - public static int CopyStream(Stream input, Stream output) + public static void CopyStream(Stream input, Stream output) { const int bufferSize = 0x4000; - byte[] buffer = new byte[bufferSize]; - int bytesCopied = 0; - int bytesRead; - do - { - bytesRead = input.Read(buffer, 0, bufferSize); - output.Write(buffer, 0, bytesRead); - bytesCopied += bytesRead; - } while (bytesRead > 0); - output.Flush(); + input.CopyTo(output, bufferSize); input.Position = 0; output.Position = 0; - return bytesCopied; } public static string FilterNonprintableChars(string value) @@ -194,9 +172,15 @@ public static Version GetTargetFrameworkVersion(string targetFramework) Version frameworkVersion = null; if (!String.IsNullOrEmpty(targetFramework)) { - if (targetFramework.StartsWith("v", StringComparison.OrdinalIgnoreCase)) + if (targetFramework[0] is 'v' or 'V') { - Version.TryParse(targetFramework.Substring(1), out frameworkVersion); + Version.TryParse( +#if NET + targetFramework.AsSpan(1), +#else + targetFramework.Substring(1), +#endif + out frameworkVersion); } else { @@ -216,8 +200,8 @@ public static string GetEmbeddedResourceString(string name) public static Stream GetEmbeddedResourceStream(string name) { Assembly a = Assembly.GetExecutingAssembly(); - Stream s = a.GetManifestResourceStream(String.Format(CultureInfo.InvariantCulture, "{0}.{1}", typeof(Util).Namespace, name)); - Debug.Assert(s != null, String.Format(CultureInfo.CurrentCulture, "EmbeddedResource '{0}' not found", name)); + Stream s = a.GetManifestResourceStream($"{typeof(Util).Namespace}.{name}"); + Debug.Assert(s != null, $"EmbeddedResource '{name}' not found"); return s; } @@ -634,8 +618,14 @@ public static Version ConvertFrameworkVersionToString(string version) { if (version.StartsWith("v", StringComparison.OrdinalIgnoreCase)) { - return new Version(version.Substring(1)); + return Version.Parse( +#if NET + version.AsSpan(1)); +#else + version.Substring(1)); +#endif } + return new Version(version); } diff --git a/src/Tasks/ManifestUtil/XmlUtil.cs b/src/Tasks/ManifestUtil/XmlUtil.cs index 32f985124aa..39c110eebb0 100644 --- a/src/Tasks/ManifestUtil/XmlUtil.cs +++ b/src/Tasks/ManifestUtil/XmlUtil.cs @@ -82,14 +82,14 @@ public static Stream XslTransform(string resource, Stream input, params Dictiona int t2 = Environment.TickCount; XPathDocument d = new XPathDocument(s); - Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "new XPathDocument(1) t={0}", Environment.TickCount - t2)); + Util.WriteLog($"new XPathDocument(1) t={Environment.TickCount - t2}"); int t3 = Environment.TickCount; var xslc = new XslCompiledTransform(); // Using the Trusted Xslt is fine as the style sheet comes from our own assemblies. // This is similar to the prior this.GetType().Assembly/Evidence method that was used in the now depricated XslTransform. xslc.Load(d, XsltSettings.TrustedXslt, s_resolver); - Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "XslCompiledTransform.Load t={0}", Environment.TickCount - t3)); + Util.WriteLog($"XslCompiledTransform.Load t={Environment.TickCount - t3}"); // Need to copy input stream because XmlReader will close it, // causing errors for later callers that access the same stream @@ -99,7 +99,7 @@ public static Stream XslTransform(string resource, Stream input, params Dictiona int t4 = Environment.TickCount; using (XmlReader reader = XmlReader.Create(clonedInput)) { - Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "new XmlReader(2) t={0}", Environment.TickCount - t4)); + Util.WriteLog($"new XmlReader(2) t={Environment.TickCount - t4}"); XsltArgumentList args = null; if (entries.Length > 0) @@ -110,7 +110,7 @@ public static Stream XslTransform(string resource, Stream input, params Dictiona string key = entry.Key.ToString(); object val = entry.Value.ToString(); args.AddParam(key, "", val); - Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "arg: key='{0}' value='{1}'", key, val.ToString())); + Util.WriteLog($"arg: key='{key}' value='{val}'"); } } @@ -122,13 +122,13 @@ public static Stream XslTransform(string resource, Stream input, params Dictiona int t5 = Environment.TickCount; xslc.Transform(reader, args, w, s_resolver); - Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "XslCompiledTransform.Transform t={0}", Environment.TickCount - t4)); + Util.WriteLog($"XslCompiledTransform.Transform t={Environment.TickCount - t4}"); w.WriteEndDocument(); w.Flush(); m.Position = 0; - Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "XslCompiledTransform(\"{0}\") t={1}", resource, Environment.TickCount - t1)); + Util.WriteLog($"XslCompiledTransform(\"{resource}\") t={Environment.TickCount - t1}"); return m; } @@ -153,7 +153,7 @@ public override Object GetEntity(Uri uri, string role, Type t) { // First look in assembly resources... Assembly a = Assembly.GetExecutingAssembly(); - s = a.GetManifestResourceStream(String.Format(CultureInfo.InvariantCulture, "{0}.{1}", typeof(Util).Namespace, filename)); + s = a.GetManifestResourceStream($"{typeof(Util).Namespace}.{filename}"); if (s != null) { @@ -191,7 +191,7 @@ public override Object GetEntity(Uri uri, string role, Type t) } // Didn't find the resource... - Debug.Fail(String.Format(CultureInfo.CurrentCulture, "ResourceResolver could not find file '{0}'", filename)); + Debug.Fail($"ResourceResolver could not find file '{filename}'"); return null; } } diff --git a/src/Tasks/ManifestUtil/mansign2.cs b/src/Tasks/ManifestUtil/mansign2.cs index 862bfb6b88c..d23e63c0870 100644 --- a/src/Tasks/ManifestUtil/mansign2.cs +++ b/src/Tasks/ManifestUtil/mansign2.cs @@ -817,12 +817,17 @@ private static string ObtainRFC3161Timestamp(string timeStampUrl, string signatu try { +#if NET + Span nonce = stackalloc byte[32]; + RandomNumberGenerator.Fill(nonce); +#else byte[] nonce = new byte[32]; using (RandomNumberGenerator rng = RandomNumberGenerator.Create()) { rng.GetBytes(nonce); } +#endif // Eventually, CryptEncodeObjectEx(...) is called on a CRYPT_TIMESTAMP_REQUEST with this nonce, // and CryptEncodeObjectEx(...) interprets the nonce as a little endian, DER-encoded integer value @@ -1044,13 +1049,19 @@ private static void StrongNameSignManifestDom(XmlDocument manifestDom, XmlDocume // Insert the signature now. signatureParent.AppendChild(xmlDigitalSignature); } + +#if !NET private static readonly char[] s_hexValues = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; +#endif private static string BytesToHexString(byte[] array, int start, int end) { string result = null; if (array != null) { +#if NET + return Convert.ToHexStringLower(array.AsSpan(start, end - start)); +#else char[] hexOrder = new char[(end - start) * 2]; int i = end; int digit, j = 0; @@ -1062,6 +1073,7 @@ private static string BytesToHexString(byte[] array, int start, int end) hexOrder[j++] = s_hexValues[digit]; } result = new String(hexOrder); +#endif } return result; } diff --git a/src/Tasks/NativeMethods.cs b/src/Tasks/NativeMethods.cs index 111880e1d30..8c761db013d 100644 --- a/src/Tasks/NativeMethods.cs +++ b/src/Tasks/NativeMethods.cs @@ -520,7 +520,7 @@ internal struct PROCESS_INFORMATION /// /// Interop methods. /// - internal static class NativeMethods + internal static partial class NativeMethods { #region Constants @@ -1160,7 +1160,7 @@ internal static unsafe bool TryReadMetadataString(string fullPath, IntPtr attrDa } // And convert it to the output string. - strValue = new String(Encoding.UTF8.GetChars(bytes)); + strValue = Encoding.UTF8.GetString(bytes); } else { @@ -1235,19 +1235,24 @@ internal static unsafe int CorSigUncompressData(IntPtr data, out int uncompresse /// This class is a wrapper over the native GAC enumeration API. /// [ComVisible(false)] - internal class AssemblyCacheEnum : IEnumerable + internal partial class AssemblyCacheEnum : IEnumerable { /// /// Path to the gac /// private static readonly string s_gacPath = Path.Combine(NativeMethodsShared.FrameworkBasePath, "gac"); + private const string AssemblyVersionPattern = @"^([.\d]+)_([^_]*)_([a-fA-F\d]{16})$"; + /// /// Regex for directory version parsing /// - private static readonly Regex s_assemblyVersionRegex = new Regex( - @"^([.\d]+)_([^_]*)_([a-fA-F\d]{16})$", - RegexOptions.CultureInvariant | RegexOptions.Compiled); +#if NET + [GeneratedRegex(AssemblyVersionPattern, RegexOptions.CultureInvariant)] + private static partial Regex AssemblyVersionRegex { get; } +#else + private static Regex AssemblyVersionRegex { get; } = new Regex(AssemblyVersionPattern, RegexOptions.CultureInvariant | RegexOptions.Compiled); +#endif /// /// The IAssemblyEnum interface which allows us to ask for the next assembly from the GAC enumeration. @@ -1387,7 +1392,7 @@ public IEnumerator GetEnumerator() var versionString = Path.GetFileName(version); if (!string.IsNullOrWhiteSpace(versionString)) { - var match = s_assemblyVersionRegex.Match(versionString); + var match = AssemblyVersionRegex.Match(versionString); if (match.Success) { var name = new AssemblyName @@ -1407,10 +1412,16 @@ public IEnumerator GetEnumerator() if (!string.IsNullOrWhiteSpace(match.Groups[3].Value)) { var value = match.Groups[3].Value; - name.SetPublicKeyToken( + byte[] key = +#if NET + Convert.FromHexString(value.AsSpan(0, 16)); +#else Enumerable.Range(0, 16) - .Where(x => x % 2 == 0) - .Select(x => Convert.ToByte(value.Substring(x, 2), 16)).ToArray()); + .Where(x => x % 2 == 0) + .Select(x => Convert.ToByte(value.Substring(x, 2), 16)) + .ToArray(); +#endif + name.SetPublicKeyToken(key); } yield return new AssemblyNameExtension(name); @@ -1455,8 +1466,12 @@ public static string AssemblyPathFromStrongName(string strongName) "{0}_{1}_{2}", assemblyNameVersion.Version.ToString(4), assemblyNameVersion.CultureName != "neutral" ? assemblyNameVersion.CultureName : string.Empty, +#if NET + Convert.ToHexStringLower(assemblyNameVersion.GetPublicKeyToken())), +#else assemblyNameVersion.GetPublicKeyToken() .Aggregate(new StringBuilder(), (builder, v) => builder.Append(v.ToString("x2")))), +#endif assemblyNameVersion.Name + ".dll"); if (FileSystems.Default.FileExists(path)) diff --git a/src/Tasks/RedistList.cs b/src/Tasks/RedistList.cs index f5476e2f9fc..7d8e6acd7ce 100644 --- a/src/Tasks/RedistList.cs +++ b/src/Tasks/RedistList.cs @@ -372,7 +372,7 @@ private static string GetSimpleName(string assemblyName) throw new ArgumentNullException(nameof(assemblyName)); } - int i = assemblyName.IndexOf(",", StringComparison.Ordinal); + int i = assemblyName.IndexOf(','); return i > 0 ? assemblyName.Substring(0, i) : assemblyName; } @@ -794,7 +794,7 @@ private static void ParseFileListSection(AssemblyTableInfo assemblyTableInfo, st { // When comparing the assembly entries we want to compare the FullName which is a formatted as name, version, publicKeyToken and culture and whether the entry is a redistroot flag // We do not need to add the redistName and the framework directory because this will be the same for all entries in the current redist list being read. - string hashIndex = String.Format(CultureInfo.InvariantCulture, "{0},{1}", newEntry.FullName, newEntry.IsRedistRoot == null ? "null" : newEntry.IsRedistRoot.ToString()); + string hashIndex = $"{newEntry.FullName},{(newEntry.IsRedistRoot == null ? "null" : newEntry.IsRedistRoot.ToString())}"; assemblyEntries.TryGetValue(hashIndex, out AssemblyEntry dictionaryEntry); // If the entry is not in the dictionary or the entry is in the dictionary but the new entry has the ingac flag true, make sure the dictionary contains the entry with the ingac true. diff --git a/src/Tasks/RequiresFramework35SP1Assembly.cs b/src/Tasks/RequiresFramework35SP1Assembly.cs index 4714f818e73..741dcbe59fc 100644 --- a/src/Tasks/RequiresFramework35SP1Assembly.cs +++ b/src/Tasks/RequiresFramework35SP1Assembly.cs @@ -79,7 +79,12 @@ private static Version ConvertFrameworkVersionToString(string version) { if (version.StartsWith("v", StringComparison.OrdinalIgnoreCase)) { - return new Version(version.Substring(1)); + return Version.Parse( +#if NET + version.AsSpan(1)); +#else + version.Substring(1)); +#endif } return new Version(version); } diff --git a/src/Tasks/ResolveComReference.cs b/src/Tasks/ResolveComReference.cs index c8ba6d686f4..c339ea9c5be 100644 --- a/src/Tasks/ResolveComReference.cs +++ b/src/Tasks/ResolveComReference.cs @@ -5,7 +5,7 @@ #if !RUNTIME_TYPE_NETCORE using System.Collections.Generic; #endif -#if !NET7_0_OR_GREATER +#if !NET using System.Diagnostics; using System.Globalization; using System.IO; diff --git a/src/Tasks/ResolveManifestFiles.cs b/src/Tasks/ResolveManifestFiles.cs index 9a78f010f16..d8a61ab1579 100644 --- a/src/Tasks/ResolveManifestFiles.cs +++ b/src/Tasks/ResolveManifestFiles.cs @@ -215,7 +215,12 @@ private static Version ConvertFrameworkVersionToString(string version) { if (version.StartsWith("v", StringComparison.OrdinalIgnoreCase)) { - return new Version(version.Substring(1)); + return Version.Parse( +#if NET + version.AsSpan(1)); +#else + version.Substring(1)); +#endif } return new Version(version); } @@ -346,7 +351,7 @@ private static CultureInfo GetItemCulture(ITaskItem item) // Infer culture from path (i.e. "obj\debug\fr\WindowsApplication1.resources.dll" -> "fr") string[] pathSegments = PathUtil.GetPathSegments(item.ItemSpec); itemCulture = pathSegments.Length > 1 ? pathSegments[pathSegments.Length - 2] : null; - Debug.Assert(!String.IsNullOrEmpty(itemCulture), String.Format(CultureInfo.CurrentCulture, "Satellite item '{0}' is missing expected attribute '{1}'", item.ItemSpec, "Culture")); + Debug.Assert(!String.IsNullOrEmpty(itemCulture), $"Satellite item '{item.ItemSpec}' is missing expected attribute 'Culture'"); item.SetMetadata("Culture", itemCulture); } return new CultureInfo(itemCulture); @@ -862,7 +867,7 @@ public void Add(ITaskItem item) // Add to map with full name, for SpecificVersion=true case string key = fusionName.ToLowerInvariant(); - Debug.Assert(!_dictionary.ContainsKey(key), String.Format(CultureInfo.CurrentCulture, "Two or more items with same key '{0}' detected", key)); + Debug.Assert(!_dictionary.ContainsKey(key), $"Two or more items with same key '{key}' detected"); if (!_dictionary.ContainsKey(key)) { _dictionary.Add(key, entry); @@ -921,7 +926,7 @@ public void Add(ITaskItem item) { // Use satellite assembly strong name signature as key string key = identity.ToString(); - Debug.Assert(!_dictionary.ContainsKey(key), String.Format(CultureInfo.CurrentCulture, "Two or more items with same key '{0}' detected", key)); + Debug.Assert(!_dictionary.ContainsKey(key), $"Two or more items with same key '{key}' detected"); if (!_dictionary.ContainsKey(key)) { _dictionary.Add(key, entry); @@ -962,7 +967,7 @@ public void Add(ITaskItem item, bool includedByDefault) } string key = targetPath.ToLowerInvariant(); - Debug.Assert(!_dictionary.ContainsKey(key), String.Format(CultureInfo.CurrentCulture, "Two or more items with same '{0}' attribute detected", ItemMetadataNames.targetPath)); + Debug.Assert(!_dictionary.ContainsKey(key), $"Two or more items with same '{(object)ItemMetadataNames.targetPath}' attribute detected"); var entry = new MapEntry(item, includedByDefault); if (!_dictionary.ContainsKey(key)) { @@ -997,11 +1002,11 @@ private static PublishState StringToPublishState(string value) } catch (FormatException) { - Debug.Fail(String.Format(CultureInfo.CurrentCulture, "Invalid value '{0}' for {1}", value, "PublishState")); + Debug.Fail($"Invalid value '{value}' for PublishState"); } catch (ArgumentException) { - Debug.Fail(String.Format(CultureInfo.CurrentCulture, "Invalid value '{0}' for {1}", value, "PublishState")); + Debug.Fail($"Invalid value '{value}' for PublishState"); } } return PublishState.Auto; @@ -1036,14 +1041,14 @@ public static PublishFlags GetAssemblyFlags(PublishState state, bool copyLocal) isPublished = false; break; case PublishState.DataFile: - Debug.Fail(String.Format(CultureInfo.CurrentCulture, "PublishState.DataFile is invalid for an assembly")); + Debug.Fail("PublishState.DataFile is invalid for an assembly"); break; case PublishState.Prerequisite: isPrerequisite = true; isPublished = false; break; default: - Debug.Fail(String.Format(CultureInfo.CurrentCulture, "Unhandled value PublishFlags.{0}", state.ToString())); + Debug.Fail($"Unhandled value PublishFlags.{state}"); break; } return new PublishFlags(isDataFile, isPrerequisite, isPublished); @@ -1073,10 +1078,10 @@ public static PublishFlags GetFileFlags(PublishState state, string fileExtension isPublished = true; break; case PublishState.Prerequisite: - Debug.Fail(String.Format(CultureInfo.CurrentCulture, "PublishState.Prerequisite is invalid for a file")); + Debug.Fail("PublishState.Prerequisite is invalid for a file"); break; default: - Debug.Fail(String.Format(CultureInfo.CurrentCulture, "Unhandled value PublishFlags.{0}", state.ToString())); + Debug.Fail($"Unhandled value PublishFlags.{state}"); break; } return new PublishFlags(isDataFile, isPrerequisite, isPublished); @@ -1103,14 +1108,14 @@ public static PublishFlags GetSatelliteFlags(PublishState state, CultureInfo sat isPublished = false; break; case PublishState.DataFile: - Debug.Fail(String.Format(CultureInfo.CurrentCulture, "PublishState.DataFile is invalid for an assembly")); + Debug.Fail("PublishState.DataFile is invalid for an assembly"); break; case PublishState.Prerequisite: isPrerequisite = true; isPublished = false; break; default: - Debug.Fail(String.Format(CultureInfo.CurrentCulture, "Unhandled value PublishFlags.{0}", state.ToString())); + Debug.Fail($"Unhandled value PublishFlags.{state}"); break; } return new PublishFlags(isDataFile, isPrerequisite, isPublished); diff --git a/src/Tasks/ResolveSDKReference.cs b/src/Tasks/ResolveSDKReference.cs index 4cf06aa29e8..6a1a82c1ced 100644 --- a/src/Tasks/ResolveSDKReference.cs +++ b/src/Tasks/ResolveSDKReference.cs @@ -20,7 +20,7 @@ namespace Microsoft.Build.Tasks /// Resolves an SDKReference to a full path on disk /// #pragma warning disable RS0022 // Constructor make noninheritable base class inheritable: Longstanding API design that we shouldn't change now - public class ResolveSDKReference : TaskExtension + public partial class ResolveSDKReference : TaskExtension #pragma warning restore RS0022 // Constructor make noninheritable base class inheritable { #region fields @@ -33,13 +33,18 @@ public class ResolveSDKReference : TaskExtension { "UAP", "Windows" } }; + private const string SdkReferenceFormatPattern = @"(?^[^,]*),\s*Version=(?.*)"; + /// /// Regex for breaking up the sdk reference include into pieces. /// Example: XNA, Version=8.0 /// - private static readonly Regex s_sdkReferenceFormat = new Regex( - @"(?^[^,]*),\s*Version=(?.*)", - RegexOptions.IgnoreCase); +#if NET + [GeneratedRegex(SdkReferenceFormatPattern, RegexOptions.IgnoreCase)] + private static partial Regex SdkReferenceFormatRegex { get; } +#else + private static Regex SdkReferenceFormatRegex { get; } = new Regex(SdkReferenceFormatPattern, RegexOptions.IgnoreCase); +#endif /// /// SimpleName group @@ -409,7 +414,7 @@ public override bool Execute() { if (!sdksAlreadyErrorOrWarnedFor.Contains(incompatibleReference) && incompatibleReference != notCompatibleReference /*cannot be incompatible with self*/) { - listOfIncompatibleReferences.Add(String.Format(CultureInfo.CurrentCulture, "\"{0}\"", incompatibleReference.SDKName)); + listOfIncompatibleReferences.Add($"\"{incompatibleReference.SDKName}\""); sdksAlreadyErrorOrWarnedFor.Add(incompatibleReference); } } @@ -438,7 +443,7 @@ public override bool Execute() { if (!sdksAlreadyErrorOrWarnedFor.Contains(incompatibleReference) && incompatibleReference != notCompatibleReference /*cannot be incompatible with self*/) { - listOfIncompatibleReferences.Add(String.Format(CultureInfo.CurrentCulture, "\"{0}\"", incompatibleReference.SDKName)); + listOfIncompatibleReferences.Add($"\"{incompatibleReference.SDKName}\""); sdksAlreadyErrorOrWarnedFor.Add(incompatibleReference); } } @@ -481,7 +486,7 @@ internal static void AddMetadataToReferences(TaskLoggingHelper log, HashSet sdkRef // Return true if no reference could be found return resolvedReference == null; }) - .Select(y => String.Format(CultureInfo.CurrentCulture, "\"{0}\"", y)) + .Select(y => $"\"{y}\"") .ToArray(); return unresolvedDependencyIdentities; @@ -574,7 +579,7 @@ internal SDKReference ParseSDKReference(ITaskItem referenceItem) /// private static bool ParseSDKReference(string reference, out string sdkSimpleName, out string rawSdkVersion) { - Match match = s_sdkReferenceFormat.Match(reference); + Match match = SdkReferenceFormatRegex.Match(reference); sdkSimpleName = String.Empty; bool parsedVersion = false; @@ -733,7 +738,7 @@ public SDKReference(ITaskItem taskItem, string sdkName, string sdkVersion) ReferenceItem = taskItem; SimpleName = sdkName; Version = sdkVersion; - SDKName = String.Format(CultureInfo.InvariantCulture, "{0}, Version={1}", SimpleName, Version); + SDKName = $"{SimpleName}, Version={Version}"; FrameworkIdentitiesFromManifest = new Dictionary(StringComparer.OrdinalIgnoreCase); AppxLocationsFromManifest = new Dictionary(StringComparer.OrdinalIgnoreCase); ResolutionErrors = new List>(); @@ -1289,13 +1294,13 @@ private void CreateResolvedReferenceItem(string targetConfiguration, string targ { // Try and find a framework identity that matches on both the configuration and architecture "FrameworkIdentity--" FrameworkIdentity = null; - string frameworkIdentityKey = String.Format(CultureInfo.InvariantCulture, "{0}-{1}-{2}", SDKManifest.Attributes.FrameworkIdentity, sdkConfiguration, sdkArchitecture); + string frameworkIdentityKey = $"{SDKManifest.Attributes.FrameworkIdentity}-{sdkConfiguration}-{sdkArchitecture}"; FrameworkIdentity = FindFrameworkIdentity(frameworkIdentityKey); // Try and find a framework identity that matches on the configuration , Element must be named "FrameworkIdentity-" only. if (FrameworkIdentity == null) { - frameworkIdentityKey = String.Format(CultureInfo.InvariantCulture, "{0}-{1}", SDKManifest.Attributes.FrameworkIdentity, sdkConfiguration); + frameworkIdentityKey = $"{SDKManifest.Attributes.FrameworkIdentity}-{sdkConfiguration}"; FrameworkIdentity = FindFrameworkIdentity(frameworkIdentityKey); } diff --git a/src/Tasks/ResourceHandling/MSBuildResXReader.cs b/src/Tasks/ResourceHandling/MSBuildResXReader.cs index ea1ffae0211..1f6b95b7067 100644 --- a/src/Tasks/ResourceHandling/MSBuildResXReader.cs +++ b/src/Tasks/ResourceHandling/MSBuildResXReader.cs @@ -89,7 +89,11 @@ private static string GetFullTypeNameFromAlias(string aliasedTypeName, Dictionar int indexStart = aliasedTypeName.IndexOf(','); if (aliases.TryGetValue(aliasedTypeName.Substring(indexStart + 2), out string fullAssemblyIdentity)) { +#if NET + return string.Concat(aliasedTypeName.AsSpan(0, indexStart + 2), fullAssemblyIdentity); +#else return aliasedTypeName.Substring(0, indexStart + 2) + fullAssemblyIdentity; +#endif } // Allow "System.String" bare @@ -290,7 +294,7 @@ private static void AddLinkedResource(string resxFilename, bool pathsRelativeToB /// private static bool IsByteArray(string fileRefType) { - return fileRefType.IndexOf("System.Byte[]") != -1 && fileRefType.IndexOf("mscorlib") != -1; + return fileRefType.Contains("System.Byte[]") && fileRefType.Contains("mscorlib"); } internal static bool IsString(string fileRefType) @@ -333,7 +337,7 @@ internal static string[] ParseResxFileRefString(string stringValue) string remainingString; if (stringValue.StartsWith("\"")) { - int lastIndexOfQuote = stringValue.LastIndexOf("\""); + int lastIndexOfQuote = stringValue.LastIndexOf('"'); if (lastIndexOfQuote - 1 < 0) { throw new ArgumentException(nameof(stringValue)); @@ -349,7 +353,7 @@ internal static string[] ParseResxFileRefString(string stringValue) } else { - int nextSemiColumn = stringValue.IndexOf(";"); + int nextSemiColumn = stringValue.IndexOf(';'); if (nextSemiColumn == -1) { throw new ArgumentException(nameof(stringValue)); diff --git a/src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactory.cs b/src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactory.cs index 535156bc1fd..d5d9ebda785 100644 --- a/src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactory.cs +++ b/src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactory.cs @@ -536,10 +536,10 @@ internal bool TryResolveAssemblyReferences(TaskLoggingHelper log, RoslynCodeTask // Start with the user specified references and include all of the default references that are language agnostic IEnumerable references = taskInfo.References.Union(DefaultReferences[String.Empty]); - if (DefaultReferences.ContainsKey(taskInfo.CodeLanguage)) + if (DefaultReferences.TryGetValue(taskInfo.CodeLanguage, out IEnumerable value)) { // Append default references for the specific language - references = references.Union(DefaultReferences[taskInfo.CodeLanguage]); + references = references.Union(value); } List directoriesToAddToAppDomain = new(); diff --git a/src/Tasks/SystemState.cs b/src/Tasks/SystemState.cs index f85dc93eb7e..cbb66f13907 100644 --- a/src/Tasks/SystemState.cs +++ b/src/Tasks/SystemState.cs @@ -70,7 +70,7 @@ internal sealed class SystemState : StateFileBase, ITranslatable /// /// Additional level of caching kept at the process level. /// - private static ConcurrentDictionary s_processWideFileStateCache = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + private static readonly ConcurrentDictionary s_processWideFileStateCache = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); /// /// XML tables of installed assemblies. diff --git a/src/Tasks/Unzip.cs b/src/Tasks/Unzip.cs index 690308135f0..01026579232 100644 --- a/src/Tasks/Unzip.cs +++ b/src/Tasks/Unzip.cs @@ -327,7 +327,7 @@ private void ParsePattern(string pattern, out string[] patterns) // Supporting property references would require access to Expander which is unavailable in Microsoft.Build.Tasks Log.LogErrorWithCodeFromResources("Unzip.ErrorParsingPatternPropertyReferences", pattern); } - else if (pattern.IndexOfAny(FileUtilities.InvalidPathChars) != -1) + else if (pattern.AsSpan().IndexOfAny(FileUtilities.InvalidPathChars) >= 0) { Log.LogErrorWithCodeFromResources("Unzip.ErrorParsingPatternInvalidPath", pattern); } diff --git a/src/Tasks/WriteCodeFragment.cs b/src/Tasks/WriteCodeFragment.cs index ecfe9bad0d6..9e08cea56bc 100644 --- a/src/Tasks/WriteCodeFragment.cs +++ b/src/Tasks/WriteCodeFragment.cs @@ -210,7 +210,13 @@ private string GenerateCode(out string extension) if (name.StartsWith("_Parameter", StringComparison.OrdinalIgnoreCase)) { - if (!Int32.TryParse(name.Substring("_Parameter".Length), out int index)) + if (!Int32.TryParse( +#if NET + name.AsSpan("_Parameter".Length), +#else + name.Substring("_Parameter".Length), +#endif + out int index)) { Log.LogErrorWithCodeFromResources("General.InvalidValue", name, "WriteCodeFragment"); return null; diff --git a/src/Tasks/XamlTaskFactory/CommandLineGenerator.cs b/src/Tasks/XamlTaskFactory/CommandLineGenerator.cs index 1ef6c1ec726..f2fbe04a6b2 100644 --- a/src/Tasks/XamlTaskFactory/CommandLineGenerator.cs +++ b/src/Tasks/XamlTaskFactory/CommandLineGenerator.cs @@ -413,22 +413,21 @@ private static void EmitStringArraySwitch(CommandLineBuilder clb, CommandLineToo /// private static bool PerformSwitchValueSubstition(CommandLineBuilder clb, CommandLineToolSwitch commandLineToolSwitch, string switchValue) { - Regex regex = new Regex(@"\[value]", RegexOptions.IgnoreCase); - Match match = regex.Match(commandLineToolSwitch.SwitchValue); - if (match.Success) + const string Value = "[value]"; + int valuePos = commandLineToolSwitch.SwitchValue.IndexOf(Value, StringComparison.OrdinalIgnoreCase); + if (valuePos >= 0) { - string prefixToAppend = commandLineToolSwitch.SwitchValue.Substring(match.Index + match.Length, commandLineToolSwitch.SwitchValue.Length - (match.Index + match.Length)); - string valueToAppend; - if (!switchValue.EndsWith("\\\\", StringComparison.OrdinalIgnoreCase) && switchValue.EndsWith("\\", StringComparison.OrdinalIgnoreCase) && prefixToAppend.Length > 0 && prefixToAppend[0] == '\"') - { - // If the combined string would create \" then we need to escape it - // if the combined string would create \\" then we ignore it as as assume it is already escaped. - valueToAppend = commandLineToolSwitch.SwitchValue.Substring(0, match.Index) + switchValue + "\\" + prefixToAppend; - } - else - { - valueToAppend = commandLineToolSwitch.SwitchValue.Substring(0, match.Index) + switchValue + prefixToAppend; - } + string prefixToAppend = commandLineToolSwitch.SwitchValue.Substring(valuePos + Value.Length); + + // If the combined string would create \" then we need to escape it + // if the combined string would create \\" then we ignore it as as assume it is already escaped. + bool needsEscaping = + !switchValue.EndsWith("\\\\", StringComparison.OrdinalIgnoreCase) && + switchValue.EndsWith("\\", StringComparison.OrdinalIgnoreCase) && + prefixToAppend.Length > 0 && + prefixToAppend[0] == '\"'; + + string valueToAppend = $"{commandLineToolSwitch.SwitchValue.Substring(0, valuePos)}{switchValue}{(needsEscaping ? "\\" : "")}{prefixToAppend}"; clb.AppendSwitch(valueToAppend); return true; @@ -645,8 +644,7 @@ private void GenerateTemplatedCommandLine(CommandLineBuilder builder) // Match all instances of [asdf], where "asdf" can be any combination of any // characters *except* a [ or an ]. i.e., if "[ [ sdf ]" is passed, then we will // match "[ sdf ]" - string matchString = @"\[[^\[\]]+\]"; - Regex regex = new Regex(matchString, RegexOptions.ECMAScript); + Regex regex = new Regex(@"\[[^\[\]]+\]", RegexOptions.ECMAScript); MatchCollection matches = regex.Matches(CommandLineTemplate); int indexOfEndOfLastSubstitution = 0; @@ -735,7 +733,7 @@ private void GenerateTemplatedCommandLine(CommandLineBuilder builder) indexOfEndOfLastSubstitution = match.Index + match.Length; } - builder.AppendTextUnquoted(CommandLineTemplate.Substring(indexOfEndOfLastSubstitution, CommandLineTemplate.Length - indexOfEndOfLastSubstitution)); + builder.AppendTextUnquoted(CommandLineTemplate.Substring(indexOfEndOfLastSubstitution)); } } } diff --git a/src/Tasks/XamlTaskFactory/XamlTaskFactory.cs b/src/Tasks/XamlTaskFactory/XamlTaskFactory.cs index e74251b8bba..18e0ce17d1d 100644 --- a/src/Tasks/XamlTaskFactory/XamlTaskFactory.cs +++ b/src/Tasks/XamlTaskFactory/XamlTaskFactory.cs @@ -71,7 +71,7 @@ public Type TaskType { if (_taskType == null) { - _taskType = _taskAssembly.GetType(String.Concat(XamlTaskNamespace, ".", TaskName), true); + _taskType = _taskAssembly.GetType($"{XamlTaskNamespace}.{TaskName}", true); } return _taskType; @@ -195,7 +195,7 @@ public bool Initialize(string taskName, IDictionary ta /// The task factory logging host will log messages in the context of the task. public ITask CreateTask(IBuildEngine taskFactoryLoggingHost) { - string fullTaskName = String.Concat(TaskNamespace, ".", TaskName); + string fullTaskName = $"{TaskNamespace}.{TaskName}"; return (ITask)_taskAssembly.CreateInstance(fullTaskName); } diff --git a/src/Utilities.UnitTests/ToolLocationHelper_Tests.cs b/src/Utilities.UnitTests/ToolLocationHelper_Tests.cs index a34a45ea3ec..7fbd0431575 100644 --- a/src/Utilities.UnitTests/ToolLocationHelper_Tests.cs +++ b/src/Utilities.UnitTests/ToolLocationHelper_Tests.cs @@ -2142,7 +2142,7 @@ public void GetPathToStandardLibraries64Bit35() string frameworkDirectory2064bit = FrameworkLocationHelper.GetPathToDotNetFrameworkV20(SharedDotNetFrameworkArchitecture.Bitness64); string frameworkDirectory20Current = FrameworkLocationHelper.GetPathToDotNetFrameworkV20(SharedDotNetFrameworkArchitecture.Current); - if (!EnvironmentUtilities.Is64BitOperatingSystem) + if (!Environment.Is64BitOperatingSystem) { // "Not 64 bit OS " return; @@ -2163,7 +2163,7 @@ public void GetPathToStandardLibraries64Bit35() pathToFramework = ToolLocationHelper.GetPathToStandardLibraries(".NetFramework", "v3.5", string.Empty, "itanium"); pathToFramework.ShouldBe(frameworkDirectory2064bit, StringCompareShould.IgnoreCase); - if (!EnvironmentUtilities.Is64BitProcess) + if (!Environment.Is64BitProcess) { pathToFramework = ToolLocationHelper.GetPathToStandardLibraries(".NetFramework", "v3.5", string.Empty, "RandomPlatform"); pathToFramework.ShouldBe(frameworkDirectory2032bit, StringCompareShould.IgnoreCase); @@ -2188,7 +2188,7 @@ public void GetPathToStandardLibraries64Bit40() { IList referencePaths = ToolLocationHelper.GetPathToReferenceAssemblies(new FrameworkNameVersioning(".NETFramework", new Version("4.0"))); - if (!EnvironmentUtilities.Is64BitOperatingSystem) + if (!Environment.Is64BitOperatingSystem) { // "Not 64 bit OS " return; @@ -2233,7 +2233,7 @@ public void GetPathToStandardLibraries32Bit35() string frameworkDirectory2032bit = FrameworkLocationHelper.GetPathToDotNetFrameworkV20(SharedDotNetFrameworkArchitecture.Bitness32); string frameworkDirectory20Current = FrameworkLocationHelper.GetPathToDotNetFrameworkV20(SharedDotNetFrameworkArchitecture.Current); - if (EnvironmentUtilities.Is64BitOperatingSystem) + if (Environment.Is64BitOperatingSystem) { // "Is a 64 bit OS " return; @@ -2271,7 +2271,7 @@ public void GetPathToStandardLibraries32Bit40() { IList referencePaths = ToolLocationHelper.GetPathToReferenceAssemblies(new FrameworkNameVersioning(".NETFramework", new Version("4.0"))); - if (EnvironmentUtilities.Is64BitOperatingSystem) + if (Environment.Is64BitOperatingSystem) { // "Is 64 bit OS " return; diff --git a/src/Utilities/CommandLineBuilder.cs b/src/Utilities/CommandLineBuilder.cs index c41b18d74c0..07ac12544e9 100644 --- a/src/Utilities/CommandLineBuilder.cs +++ b/src/Utilities/CommandLineBuilder.cs @@ -154,14 +154,14 @@ public CommandLineBuilder(bool quoteHyphensOnCommandLine, bool useNewLineSeparat /// /// Use a private property so that we can lazy initialize the regex /// - private Regex DefinitelyNeedQuotes => _definitelyNeedQuotes - ?? (_definitelyNeedQuotes = new Regex(_quoteHyphens ? s_definitelyNeedQuotesRegexWithHyphen : s_definitelyNeedQuotesRegexNoHyphen, RegexOptions.CultureInvariant)); + private Regex DefinitelyNeedQuotes => _definitelyNeedQuotes ??= + new Regex(_quoteHyphens ? s_definitelyNeedQuotesRegexWithHyphen : s_definitelyNeedQuotesRegexNoHyphen, RegexOptions.CultureInvariant); /// /// Use a private getter property to we can lazy initialize the regex /// - private Regex AllowedUnquoted => _allowedUnquoted - ?? (_allowedUnquoted = new Regex(_quoteHyphens ? s_allowedUnquotedRegexNoHyphen : s_allowedUnquotedRegexWithHyphen, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)); + private Regex AllowedUnquoted => _allowedUnquoted ??= + new Regex(_quoteHyphens ? s_allowedUnquotedRegexNoHyphen : s_allowedUnquotedRegexWithHyphen, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); /// /// Checks the given switch parameter to see if it must/can be quoted. @@ -259,6 +259,9 @@ protected void AppendQuotedTextToBuffer(StringBuilder buffer, string unquotedTex } // Count the number of quotes +#if NET + int literalQuotes = unquotedTextToAppend.AsSpan().Count('"'); +#else int literalQuotes = 0; for (int i = 0; i < unquotedTextToAppend.Length; i++) { @@ -267,6 +270,8 @@ protected void AppendQuotedTextToBuffer(StringBuilder buffer, string unquotedTex literalQuotes++; } } +#endif + if (literalQuotes > 0) { // Replace any \" sequences with \\" diff --git a/src/Utilities/LockCheck.cs b/src/Utilities/LockCheck.cs index 7b191851e70..f9ec08b314a 100644 --- a/src/Utilities/LockCheck.cs +++ b/src/Utilities/LockCheck.cs @@ -241,7 +241,7 @@ public override bool Equals(object obj) public override string ToString() { - return ProcessId + "@" + StartTime.ToString("s"); + return $"{ProcessId}@{StartTime:s}"; } } diff --git a/src/Utilities/SDKManifest.cs b/src/Utilities/SDKManifest.cs index 86e0be400ce..d8c06a2c234 100644 --- a/src/Utilities/SDKManifest.cs +++ b/src/Utilities/SDKManifest.cs @@ -45,22 +45,22 @@ public class SDKManifest /// /// Pattern in path to extension SDK used to help determine if manifest is from a framework SDK /// - private static string s_extensionSDKPathPattern = @"\MICROSOFT SDKS\WINDOWS\V8.0\EXTENSIONSDKS"; + private const string s_extensionSDKPathPattern = @"\MICROSOFT SDKS\WINDOWS\V8.0\EXTENSIONSDKS"; /// /// Default version of MaxPlatformVersion in framework extension SDKs with manifest not containing such a property /// - private static string s_defaultMaxPlatformVersion = "8.0"; + private const string s_defaultMaxPlatformVersion = "8.0"; /// /// Default version of MinOSVersion in framework extension SDKs with manifest not containing such a property /// - private static string s_defaultMinOSVersion = "6.2.1"; + private const string s_defaultMinOSVersion = "6.2.1"; /// /// Default version of MaxOSVersionTested in framework extension SDKs with manifest not containing such a property /// - private static string s_defaultMaxOSVersionTested = "6.2.1"; + private const string s_defaultMaxOSVersionTested = "6.2.1"; /// /// What should happen if this sdk is resolved with other sdks of the same productfamily or same sdk name. diff --git a/src/Utilities/TargetPlatformSDK.cs b/src/Utilities/TargetPlatformSDK.cs index 8740fe39e10..f0c7992c74b 100644 --- a/src/Utilities/TargetPlatformSDK.cs +++ b/src/Utilities/TargetPlatformSDK.cs @@ -189,6 +189,6 @@ public bool ContainsPlatform(string targetPlatformIdentifier, string targetPlatf /// /// Given an identifier and version, construct a string to use as a key for that combination. /// - internal static string GetSdkKey(string sdkIdentifier, string sdkVersion) => string.Format(CultureInfo.InvariantCulture, "{0}, Version={1}", sdkIdentifier, sdkVersion); + internal static string GetSdkKey(string sdkIdentifier, string sdkVersion) => $"{sdkIdentifier}, Version={sdkVersion}"; } } diff --git a/src/Utilities/ToolLocationHelper.cs b/src/Utilities/ToolLocationHelper.cs index 3f13658e0dd..598b9539c43 100644 --- a/src/Utilities/ToolLocationHelper.cs +++ b/src/Utilities/ToolLocationHelper.cs @@ -1821,7 +1821,7 @@ public static string GetPathToStandardLibraries(string targetFrameworkIdentifier if (NativeMethodsShared.IsWindows && platformTarget != null) { // If we are a 32 bit operating system the we should always return the 32 bit directory, or we are targeting x86, arm is also 32 bit - if (!EnvironmentUtilities.Is64BitOperatingSystem || platformTarget.Equals("x86", StringComparison.OrdinalIgnoreCase) || platformTarget.Equals("arm", StringComparison.OrdinalIgnoreCase)) + if (!Environment.Is64BitOperatingSystem || platformTarget.Equals("x86", StringComparison.OrdinalIgnoreCase) || platformTarget.Equals("arm", StringComparison.OrdinalIgnoreCase)) { targetedArchitecture = SharedDotNetFrameworkArchitecture.Bitness32; } @@ -2863,7 +2863,7 @@ private static void GatherSDKListFromRegistry(string registryRoot, Dictionary GetFrameworkVersions(string frameworkReferenceRoot, // only add if the version folder name is of the right format if (folder.Name.Length >= 4 && folder.Name.StartsWith("v", StringComparison.OrdinalIgnoreCase)) { - Version ver; - if (Version.TryParse(folder.Name.Substring(1), out ver)) + if (Version.TryParse( +#if NET + folder.Name.AsSpan(1), +#else + folder.Name.Substring(1), +#endif + out _)) { frameworkVersions.Add(folder.Name); } @@ -3982,10 +3991,14 @@ private VersionComparer() public int Compare(string versionX, string versionY) { +#if NET + return Version.Parse(versionX.AsSpan(1)).CompareTo(Version.Parse(versionY.AsSpan(1))); +#else return new Version(versionX.Substring(1)).CompareTo(new Version(versionY.Substring(1))); +#endif } } - #endregion +#endregion } }