diff --git a/src/Build.UnitTests/BuildEventArgsSerialization_Tests.cs b/src/Build.UnitTests/BuildEventArgsSerialization_Tests.cs
index 7df03d42322..6f963797536 100644
--- a/src/Build.UnitTests/BuildEventArgsSerialization_Tests.cs
+++ b/src/Build.UnitTests/BuildEventArgsSerialization_Tests.cs
@@ -7,6 +7,7 @@
using System.Globalization;
using System.IO;
using System.Linq;
+using System.Runtime.Serialization;
using System.Text;
using FluentAssertions;
using Microsoft.Build.BackEnd;
@@ -875,6 +876,98 @@ public void RoundtripTaskParameterEventArgs()
e => TranslationHelpers.GetItemsString(e.Items));
}
+ [Fact]
+ public void AbsolutePathTaskParameterTextUsesOriginalValue()
+ {
+ var basePath = new AbsolutePath(Path.GetFullPath("."));
+ var path = new AbsolutePath("input.txt", basePath);
+
+ ItemGroupLoggingHelper.GetStringFromParameterValue(path).ShouldBe("input.txt");
+ }
+
+ [Fact]
+ public void TaskParameterEventForwardingPreservesAbsolutePathOriginalValue()
+ {
+ TaskParameterEventArgs args = CreateAbsolutePathTaskParameterEventArgs();
+ var memoryStream = new MemoryStream();
+ using (var binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true))
+ {
+ args.WriteToStream(binaryWriter);
+ }
+
+ memoryStream.Position = 0;
+#pragma warning disable SYSLIB0050 // Required to exercise the legacy event forwarding deserializer.
+ var forwardedArgs = (TaskParameterEventArgs)FormatterServices.GetUninitializedObject(typeof(TaskParameterEventArgs));
+#pragma warning restore SYSLIB0050
+ using (var binaryReader = new BinaryReader(memoryStream, Encoding.UTF8, leaveOpen: true))
+ {
+ forwardedArgs.CreateFromStream(binaryReader, version: 0);
+ }
+
+ forwardedArgs.Items.Count.ShouldBe(1);
+ ((ITaskItem)forwardedArgs.Items[0]).ItemSpec.ShouldBe("input.txt");
+ }
+
+ [Fact]
+ public void BinaryLogSerializationPreservesAbsolutePathOriginalValue()
+ {
+ TaskParameterEventArgs args = CreateAbsolutePathTaskParameterEventArgs();
+ var memoryStream = new MemoryStream();
+ using (var binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true))
+ {
+ new BuildEventArgsWriter(binaryWriter).Write(args);
+ }
+
+ memoryStream.Position = 0;
+ using var reader = new BinaryReader(memoryStream, Encoding.UTF8, leaveOpen: true);
+ using var eventArgsReader = new BuildEventArgsReader(reader, BinaryLogger.FileFormatVersion);
+ var replayedArgs = (TaskParameterEventArgs)eventArgsReader.Read();
+
+ replayedArgs.Items.Count.ShouldBe(1);
+ ((ITaskItem)replayedArgs.Items[0]).ItemSpec.ShouldBe("input.txt");
+ replayedArgs.Message.ShouldContain("input.txt");
+ replayedArgs.Message.ShouldNotContain(Path.GetFullPath("input.txt"));
+ }
+
+ [Fact]
+ public void BinaryLogSerializationWritesEmptyItemSpecForDefaultAbsolutePath()
+ {
+ var args = new TaskParameterEventArgs(
+ TaskParameterMessageKind.TaskInput,
+ "File",
+ propertyName: null,
+ "File",
+ new object[] { default(AbsolutePath) },
+ logItemMetadata: false,
+ DateTime.MinValue);
+ var memoryStream = new MemoryStream();
+ using (var binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true))
+ {
+ new BuildEventArgsWriter(binaryWriter).Write(args);
+ }
+
+ memoryStream.Position = 0;
+ using var reader = new BinaryReader(memoryStream, Encoding.UTF8, leaveOpen: true);
+ using var eventArgsReader = new BuildEventArgsReader(reader, BinaryLogger.FileFormatVersion);
+ var replayedArgs = (TaskParameterEventArgs)eventArgsReader.Read();
+
+ replayedArgs.Items.Count.ShouldBe(1);
+ ((ITaskItem)replayedArgs.Items[0]).ItemSpec.ShouldBe(string.Empty);
+ }
+
+ private static TaskParameterEventArgs CreateAbsolutePathTaskParameterEventArgs()
+ {
+ var basePath = new AbsolutePath(Path.GetFullPath("."));
+ return new TaskParameterEventArgs(
+ TaskParameterMessageKind.TaskInput,
+ "File",
+ propertyName: null,
+ "File",
+ new object[] { new AbsolutePath("input.txt", basePath) },
+ logItemMetadata: false,
+ DateTime.MinValue);
+ }
+
[Fact]
public void RoundtripProjectEvaluationStartedEventArgs()
{
diff --git a/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/ItemGroupLoggingHelper.cs b/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/ItemGroupLoggingHelper.cs
index 2fd03538738..9038bb76d72 100644
--- a/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/ItemGroupLoggingHelper.cs
+++ b/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/ItemGroupLoggingHelper.cs
@@ -243,6 +243,14 @@ private static void AppendStringFromParameterValue(ReuseableStringBuilder sb, ob
keyValuePairList.Clear();
}
}
+ else if (parameterValue is AbsolutePath absolutePath)
+ {
+ sb.Append(absolutePath.OriginalValue);
+ }
+ else if (TaskItemTypeDetector.IsSupportedPathType(parameterValue.GetType()))
+ {
+ sb.Append(ValueTypeParser.ToString(parameterValue));
+ }
else if (parameterValue.GetType().IsValueType)
{
sb.Append((string)Convert.ChangeType(parameterValue, typeof(string), CultureInfo.CurrentCulture));
diff --git a/src/Build/Logging/BinaryLogger/BuildEventArgsWriter.cs b/src/Build/Logging/BinaryLogger/BuildEventArgsWriter.cs
index cba8208d74d..06b0d64d739 100644
--- a/src/Build/Logging/BinaryLogger/BuildEventArgsWriter.cs
+++ b/src/Build/Logging/BinaryLogger/BuildEventArgsWriter.cs
@@ -987,6 +987,11 @@ private void WriteTaskItemList(IEnumerable items, bool writeMetadata = true)
Write((byte)0);
}
}
+ else if (item is AbsolutePath absolutePath)
+ {
+ WriteDeduplicatedString(absolutePath.OriginalValue ?? string.Empty);
+ Write(0);
+ }
else
{
WriteDeduplicatedString(item?.ToString() ?? ""); // itemspec
diff --git a/src/Framework/TaskItem_T.cs b/src/Framework/TaskItem_T.cs
index b54706b1d21..4c418004ec1 100644
--- a/src/Framework/TaskItem_T.cs
+++ b/src/Framework/TaskItem_T.cs
@@ -70,8 +70,15 @@ public TaskItem(ITaskItem item)
/// Returns the FullPath metadata value if non-empty, otherwise falls back to itemSpec.
private static string GetFullPathOrItemSpec(ITaskItem item, string itemSpec)
{
- string fullPath = item.GetMetadata("FullPath");
- return !string.IsNullOrEmpty(fullPath) ? fullPath : itemSpec;
+ try
+ {
+ string fullPath = item.GetMetadata("FullPath");
+ return !string.IsNullOrEmpty(fullPath) ? fullPath : itemSpec;
+ }
+ catch (InvalidOperationException ex)
+ {
+ throw new ArgumentException($"Cannot create TaskItem<{typeof(T).Name}> from the provided item.", nameof(item), ex);
+ }
}
///
diff --git a/src/Framework/TaskParameterEventArgs.cs b/src/Framework/TaskParameterEventArgs.cs
index 52f623017af..fdb54492bb9 100644
--- a/src/Framework/TaskParameterEventArgs.cs
+++ b/src/Framework/TaskParameterEventArgs.cs
@@ -220,6 +220,11 @@ private void WriteItem(BinaryWriter writer, object item)
writer.Write7BitEncodedInt(0);
}
}
+ else if (item is AbsolutePath absolutePath)
+ {
+ writer.Write(absolutePath.OriginalValue ?? string.Empty);
+ writer.Write7BitEncodedInt(0);
+ }
else // string or ValueType
{
writer.Write(item?.ToString() ?? "");
diff --git a/src/Utilities.UnitTests/TaskItem_Tests.cs b/src/Utilities.UnitTests/TaskItem_Tests.cs
index 1b42c8d3169..6b92ce90bc2 100644
--- a/src/Utilities.UnitTests/TaskItem_Tests.cs
+++ b/src/Utilities.UnitTests/TaskItem_Tests.cs
@@ -486,6 +486,14 @@ public void FromITaskItem_PathLikeType_UsesFullPathMetadata()
item.Value.FullName.ShouldBe(expectedAbsolutePath);
}
+ [Fact]
+ public void FromITaskItem_InvalidPath_ThrowsArgumentException()
+ {
+ var backingItem = new TaskItem("bad\0path");
+
+ Should.Throw(() => new TaskItem(backingItem));
+ }
+
[Fact]
public void FromITaskItem_NonPathType_UsesItemSpec()
{