diff --git a/src/Build.UnitTests/Evaluation/Expander_Tests.cs b/src/Build.UnitTests/Evaluation/Expander_Tests.cs index 67d00c5b443..31dea1590f8 100644 --- a/src/Build.UnitTests/Evaluation/Expander_Tests.cs +++ b/src/Build.UnitTests/Evaluation/Expander_Tests.cs @@ -4382,6 +4382,18 @@ public void PropertyFunctionCheckFeatureAvailability(string featureName, string Assert.Equal(availability, result); } + [Theory] + [InlineData("\u3407\ud840\udc60\ud86a\ude30\ud86e\udc0a\ud86e\udda0\ud879\udeae\u2fd5\u0023", 0, 3, "\u3407\ud840\udc60\ud86a\ude30")] + [InlineData("\u3407\ud840\udc60\ud86a\ude30\ud86e\udc0a\ud86e\udda0\ud879\udeae\u2fd5\u0023", 2, 5, "\ud86a\ude30\ud86e\udc0a\ud86e\udda0\ud879\udeae\u2fd5")] + public void SubstringByTextElements(string featureName, int start, int length, string expected) + { + var expander = new Expander(new PropertyDictionary(), FileSystems.Default); + + var result = expander.ExpandIntoStringLeaveEscaped($"$([MSBuild]::SubstringByTextElements({featureName}, {start}, {length}))", ExpanderOptions.ExpandProperties, MockElementLocation.Instance); + + Assert.Equal(expected, result); + } + [Fact] public void PropertyFunctionIntrinsicFunctionGetCurrentToolsDirectory() { diff --git a/src/Build/Evaluation/Expander.cs b/src/Build/Evaluation/Expander.cs index eae0683a824..ffbdbb5e855 100644 --- a/src/Build/Evaluation/Expander.cs +++ b/src/Build/Evaluation/Expander.cs @@ -4229,6 +4229,14 @@ private bool TryExecuteWellKnownFunction(out object returnVal, object objectInst return true; } } + else if (string.Equals(_methodMethodName, nameof(IntrinsicFunctions.SubstringByTextElements), StringComparison.OrdinalIgnoreCase)) + { + if (TryGetArgs(args, out string arg0, out int arg1, out int arg2)) + { + returnVal = IntrinsicFunctions.SubstringByTextElements(arg0, arg1, arg2); + return true; + } + } else if (string.Equals(_methodMethodName, nameof(IntrinsicFunctions.CheckFeatureAvailability), StringComparison.OrdinalIgnoreCase)) { if (TryGetArg(args, out string arg0)) @@ -4560,6 +4568,32 @@ private bool TryGetArgs(object[] args, out string arg0, out string arg1) return false; } + private static bool TryGetArgs(object[] args, out string arg0, out int arg1, out int arg2) + { + arg0 = null; + arg1 = 0; + arg2 = 0; + + if (args.Length != 3) + { + return false; + } + + var value1 = args[1] as string; + var value2 = args[2] as string; + arg0 = args[0] as string; + if (value1 != null && + value2 != null && + arg0 != null && + int.TryParse(value1, out arg1) && + int.TryParse(value2, out arg2)) + { + return true; + } + + return false; + } + private static bool TryGetArg(object[] args, out int arg0) { if (args.Length != 1) diff --git a/src/Build/Evaluation/IntrinsicFunctions.cs b/src/Build/Evaluation/IntrinsicFunctions.cs index 13901dfddce..611b8af238d 100644 --- a/src/Build/Evaluation/IntrinsicFunctions.cs +++ b/src/Build/Evaluation/IntrinsicFunctions.cs @@ -9,6 +9,7 @@ using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; +using System.Globalization; using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Experimental.BuildCheck; using Microsoft.Build.Framework; @@ -628,6 +629,12 @@ internal static bool AreFeaturesEnabled(Version wave) return ChangeWaves.AreFeaturesEnabled(wave); } + internal static string SubstringByTextElements(string input, int start, int length) + { + StringInfo stringInfo = new StringInfo(input); + return stringInfo.SubstringByTextElements(start, length); + } + internal static string CheckFeatureAvailability(string featureName) { return Features.CheckFeatureAvailability(featureName).ToString(); diff --git a/src/Tasks/Microsoft.Common.CurrentVersion.targets b/src/Tasks/Microsoft.Common.CurrentVersion.targets index 1c931d712d0..3fc9a9c4d00 100644 --- a/src/Tasks/Microsoft.Common.CurrentVersion.targets +++ b/src/Tasks/Microsoft.Common.CurrentVersion.targets @@ -388,8 +388,8 @@ Copyright (C) Microsoft Corporation. All rights reserved. $(MSBuildProjectFile) - - $(MSBuildProjectFile.Substring(0,8)).$([MSBuild]::StableStringHash($(MSBuildProjectFile)).ToString("X8")) + + $([MSBuild]::SubstringByTextElements($(MSBuildProjectFile), 0, 8)).$([MSBuild]::StableStringHash($(MSBuildProjectFile)).ToString("X8")) $(MSBuildCopyMarkerName).Up2Date