From bc1e643bed7cd177062181e5199ecfdcfd28f2eb Mon Sep 17 00:00:00 2001
From: Ryland <41491307+ryalanms@users.noreply.github.com>
Date: Thu, 17 Sep 2020 14:34:01 -0700
Subject: [PATCH] Support PackageReferences in WPF projects (#3585):
Consolidate old and new temporary assembly compilation paths in to a single
task
---
.../Internal/MarkupCompiler/MarkupCompiler.cs | 18 +-
.../Internal/MarkupCompiler/PathInternal.cs | 390 ++++++++++++++++++
.../Microsoft.WinFX.targets | 49 ++-
.../GenerateTemporaryTargetAssembly.cs | 194 +++++++++
.../PresentationBuildTasks.csproj | 2 +
5 files changed, 633 insertions(+), 20 deletions(-)
create mode 100644 src/Microsoft.DotNet.Wpf/src/PresentationBuildTasks/MS/Internal/MarkupCompiler/PathInternal.cs
diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationBuildTasks/MS/Internal/MarkupCompiler/MarkupCompiler.cs b/src/Microsoft.DotNet.Wpf/src/PresentationBuildTasks/MS/Internal/MarkupCompiler/MarkupCompiler.cs
index 8adf6cf0664..556ea8e7850 100644
--- a/src/Microsoft.DotNet.Wpf/src/PresentationBuildTasks/MS/Internal/MarkupCompiler/MarkupCompiler.cs
+++ b/src/Microsoft.DotNet.Wpf/src/PresentationBuildTasks/MS/Internal/MarkupCompiler/MarkupCompiler.cs
@@ -1575,19 +1575,11 @@ private string ParentFolderPrefix
{
get
{
- string parentFolderPrefix = string.Empty;
- if (TargetPath.StartsWith(SourceFileInfo.SourcePath, StringComparison.OrdinalIgnoreCase))
- {
- string relPath = TargetPath.Substring(SourceFileInfo.SourcePath.Length);
- relPath += SourceFileInfo.RelativeSourceFilePath;
- string[] dirs = relPath.Split(new Char[] { Path.DirectorySeparatorChar });
- for (int i = 1; i < dirs.Length; i++)
- {
- parentFolderPrefix += PARENTFOLDER;
- }
- }
-
- return parentFolderPrefix;
+#if NETFX
+ return PathInternal.GetRelativePath(TargetPath, SourceFileInfo.SourcePath, StringComparison.OrdinalIgnoreCase) + Path.DirectorySeparatorChar;
+#else
+ return Path.GetRelativePath(TargetPath, SourceFileInfo.SourcePath) + Path.DirectorySeparatorChar;
+#endif
}
}
diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationBuildTasks/MS/Internal/MarkupCompiler/PathInternal.cs b/src/Microsoft.DotNet.Wpf/src/PresentationBuildTasks/MS/Internal/MarkupCompiler/PathInternal.cs
new file mode 100644
index 00000000000..7d187963b42
--- /dev/null
+++ b/src/Microsoft.DotNet.Wpf/src/PresentationBuildTasks/MS/Internal/MarkupCompiler/PathInternal.cs
@@ -0,0 +1,390 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+//---------------------------------------------------------------------------
+//
+// Description:
+// Returns a relative path from one path to another.
+//
+// Paths are resolved by calling the GetFullPath method before calculating
+// the difference. The method uses the default file path comparison for the
+// current platform (StringComparison.OrdinalIgnoreCase for Windows.)
+//
+// Ported to .NET Framework from the GetRelativePath implementation in:
+//
+// https://github.com/dotnet/corefx/blob/b123ba4b9107c73cbc02010dc1ee78eb8ffccb93/src/Common/src/CoreLib/System/IO/Path.cs
+//
+// This ported method ('GetRelativePath') is only called by the .NET Framework
+// version of PresentationBuildTasks.dll that ships with the Microsoft.NET.Sdk.WindowsDesktop
+// SDK as part of .NET 5.0.
+//
+//---------------------------------------------------------------------------
+
+#pragma warning disable 1634, 1691
+
+using System;
+using System.Xml;
+using System.IO;
+using System.Text;
+using System.Reflection;
+using System.Globalization;
+using System.ComponentModel;
+using System.Security.Cryptography;
+
+using System.CodeDom;
+using System.CodeDom.Compiler;
+using System.Collections;
+using System.Collections.Generic;
+using System.ComponentModel.Design.Serialization;
+
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+
+using System.Threading;
+using MS.Internal.Markup;
+using MS.Internal.Tasks;
+using MS.Utility; // for SR
+using Microsoft.Build.Utilities;
+using Microsoft.Build.Tasks.Windows;
+using System.Runtime.CompilerServices;
+
+namespace MS.Internal
+{
+ internal sealed class PathInternal
+ {
+ internal static string GetRelativePath(string relativeTo, string path, StringComparison comparisonType)
+ {
+ if (relativeTo == null)
+ throw new ArgumentNullException(nameof(relativeTo));
+
+ if (PathInternal.IsEffectivelyEmpty(relativeTo.AsSpan()))
+ throw new ArgumentException(nameof(relativeTo));
+
+ if (path == null)
+ throw new ArgumentNullException(nameof(path));
+
+ if (PathInternal.IsEffectivelyEmpty(path.AsSpan()))
+ throw new ArgumentException(nameof(path));
+
+ Debug.Assert(comparisonType == StringComparison.Ordinal || comparisonType == StringComparison.OrdinalIgnoreCase);
+
+ relativeTo = Path.GetFullPath(relativeTo);
+ path = Path.GetFullPath(path);
+
+ // Need to check if the roots are different- if they are we need to return the "to" path.
+ if (!PathInternal.AreRootsEqual(relativeTo, path, comparisonType))
+ return path;
+
+ int commonLength = PathInternal.GetCommonPathLength(relativeTo, path, ignoreCase: comparisonType == StringComparison.OrdinalIgnoreCase);
+
+ // If there is nothing in common they can't share the same root, return the "to" path as is.
+ if (commonLength == 0)
+ return path;
+
+ // Trailing separators aren't significant for comparison
+ int relativeToLength = relativeTo.Length;
+ if (DoesEndInDirectorySeparator(relativeTo.AsSpan()))
+ relativeToLength--;
+
+ bool pathEndsInSeparator = DoesEndInDirectorySeparator(path.AsSpan());
+ int pathLength = path.Length;
+ if (pathEndsInSeparator)
+ pathLength--;
+
+ // If we have effectively the same path, return "."
+ if (relativeToLength == pathLength && commonLength >= relativeToLength) return CurrentDir.ToString();
+
+ // We have the same root, we need to calculate the difference now using the
+ // common Length and Segment count past the length.
+ //
+ // Some examples:
+ //
+ // C:\Foo C:\Bar L3, S1 -> ..\Bar
+ // C:\Foo C:\Foo\Bar L6, S0 -> Bar
+ // C:\Foo\Bar C:\Bar\Bar L3, S2 -> ..\..\Bar\Bar
+ // C:\Foo\Foo C:\Foo\Bar L7, S1 -> ..\Bar
+
+ var sb = new StringBuilder();
+
+ // Add parent segments for segments past the common on the "from" path
+ if (commonLength < relativeToLength)
+ {
+ sb.Append(ParentDir);
+
+ for (int i = commonLength + 1; i < relativeToLength; i++)
+ {
+ if (PathInternal.IsDirectorySeparator(relativeTo[i]))
+ {
+ sb.Append(Path.DirectorySeparatorChar);
+ sb.Append(ParentDir);
+ }
+ }
+ }
+ else if (PathInternal.IsDirectorySeparator(path[commonLength]))
+ {
+ // No parent segments and we need to eat the initial separator
+ // (C:\Foo C:\Foo\Bar case)
+ commonLength++;
+ }
+
+ // Now add the rest of the "to" path, adding back the trailing separator
+ int differenceLength = pathLength - commonLength;
+ if (pathEndsInSeparator)
+ differenceLength++;
+
+ if (differenceLength > 0)
+ {
+ if (sb.Length > 0)
+ {
+ sb.Append(Path.DirectorySeparatorChar);
+ }
+
+ sb.Append(path, commonLength, differenceLength);
+ }
+
+ return sb.ToString();
+ }
+
+ ///
+ /// True if the given character is a directory separator.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static bool IsDirectorySeparator(char c)
+ {
+ return c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar;
+ }
+
+ ///
+ /// Get the common path length from the start of the string.
+ ///
+ internal static int GetCommonPathLength(string first, string second, bool ignoreCase)
+ {
+ int commonChars = EqualStartingCharacterCount(first, second, ignoreCase);
+
+ // If nothing matches
+ if (commonChars == 0)
+ return commonChars;
+
+ // Or we're a full string and equal length or match to a separator
+ if (commonChars == first.Length
+ && (commonChars == second.Length || IsDirectorySeparator(second[commonChars])))
+ return commonChars;
+
+ if (commonChars == second.Length && IsDirectorySeparator(first[commonChars]))
+ return commonChars;
+
+ // It's possible we matched somewhere in the middle of a segment e.g. C:\Foodie and C:\Foobar.
+ while (commonChars > 0 && !IsDirectorySeparator(first[commonChars - 1]))
+ commonChars--;
+
+ return commonChars;
+ }
+
+ ///
+ /// Returns true if the two paths have the same root
+ ///
+ internal static bool AreRootsEqual(string first, string second, StringComparison comparisonType)
+ {
+ int firstRootLength = GetRootLength(first.AsSpan());
+ int secondRootLength = GetRootLength(second.AsSpan());
+
+ return firstRootLength == secondRootLength
+ && string.Compare(
+ strA: first,
+ indexA: 0,
+ strB: second,
+ indexB: 0,
+ length: firstRootLength,
+ comparisonType: comparisonType) == 0;
+ }
+
+ ///
+ /// Returns true if the path is effectively empty for the current OS.
+ /// For unix, this is empty or null. For Windows, this is empty, null, or
+ /// just spaces ((char)32).
+ ///
+ internal static bool IsEffectivelyEmpty(ReadOnlySpan path)
+ {
+ if (path.IsEmpty)
+ return true;
+
+ foreach (char c in path)
+ {
+ if (c != ' ')
+ return false;
+ }
+ return true;
+ }
+
+
+ ///
+ /// Gets the length of the root of the path (drive, share, etc.).
+ ///
+ internal static int GetRootLength(ReadOnlySpan path)
+ {
+ int pathLength = path.Length;
+ int i = 0;
+
+ bool deviceSyntax = IsDevice(path);
+ bool deviceUnc = deviceSyntax && IsDeviceUNC(path);
+
+ if ((!deviceSyntax || deviceUnc) && pathLength > 0 && IsDirectorySeparator(path[0]))
+ {
+ // UNC or simple rooted path (e.g. "\foo", NOT "\\?\C:\foo")
+ if (deviceUnc || (pathLength > 1 && IsDirectorySeparator(path[1])))
+ {
+ // UNC (\\?\UNC\ or \\), scan past server\share
+
+ // Start past the prefix ("\\" or "\\?\UNC\")
+ i = deviceUnc ? UncExtendedPrefixLength : UncPrefixLength;
+
+ // Skip two separators at most
+ int n = 2;
+ while (i < pathLength && (!IsDirectorySeparator(path[i]) || --n > 0))
+ i++;
+ }
+ else
+ {
+ // Current drive rooted (e.g. "\foo")
+ i = 1;
+ }
+ }
+ else if (deviceSyntax)
+ {
+ // Device path (e.g. "\\?\.", "\\.\")
+ // Skip any characters following the prefix that aren't a separator
+ i = DevicePrefixLength;
+ while (i < pathLength && !IsDirectorySeparator(path[i]))
+ i++;
+
+ // If there is another separator take it, as long as we have had at least one
+ // non-separator after the prefix (e.g. don't take "\\?\\", but take "\\?\a\")
+ if (i < pathLength && i > DevicePrefixLength && IsDirectorySeparator(path[i]))
+ i++;
+ }
+ else if (pathLength >= 2
+ && path[1] == VolumeSeparatorChar
+ && IsValidDriveChar(path[0]))
+ {
+ // Valid drive specified path ("C:", "D:", etc.)
+ i = 2;
+
+ // If the colon is followed by a directory separator, move past it (e.g "C:\")
+ if (pathLength > 2 && IsDirectorySeparator(path[2]))
+ i++;
+ }
+
+ return i;
+ }
+
+ ///
+ /// Gets the count of common characters from the left optionally ignoring case
+ ///
+ internal static unsafe int EqualStartingCharacterCount(string first, string second, bool ignoreCase)
+ {
+ if (string.IsNullOrEmpty(first) || string.IsNullOrEmpty(second)) return 0;
+
+ int commonChars = 0;
+
+ fixed (char* f = first)
+ fixed (char* s = second)
+ {
+ char* l = f;
+ char* r = s;
+ char* leftEnd = l + first.Length;
+ char* rightEnd = r + second.Length;
+
+ while (l != leftEnd && r != rightEnd
+ && (*l == *r || (ignoreCase && char.ToUpperInvariant((*l)) == char.ToUpperInvariant((*r)))))
+ {
+ commonChars++;
+ l++;
+ r++;
+ }
+ }
+
+ return commonChars;
+ }
+
+ ///
+ /// Returns true if the path uses any of the DOS device path syntaxes. ("\\.\", "\\?\", or "\??\")
+ ///
+ internal static bool IsDevice(ReadOnlySpan path)
+ {
+ // If the path begins with any two separators is will be recognized and normalized and prepped with
+ // "\??\" for internal usage correctly. "\??\" is recognized and handled, "/??/" is not.
+ return IsExtended(path)
+ ||
+ (
+ path.Length >= DevicePrefixLength
+ && IsDirectorySeparator(path[0])
+ && IsDirectorySeparator(path[1])
+ && (path[2] == '.' || path[2] == '?')
+ && IsDirectorySeparator(path[3])
+ );
+ }
+
+ ///
+ /// Returns true if the path is a device UNC (\\?\UNC\, \\.\UNC\)
+ ///
+ internal static bool IsDeviceUNC(ReadOnlySpan path)
+ {
+ return path.Length >= UncExtendedPrefixLength
+ && IsDevice(path)
+ && IsDirectorySeparator(path[7])
+ && path[4] == 'U'
+ && path[5] == 'N'
+ && path[6] == 'C';
+ }
+
+ ///
+ /// Returns true if the given character is a valid drive letter
+ ///
+ internal static bool IsValidDriveChar(char value)
+ {
+ return ((value >= 'A' && value <= 'Z') || (value >= 'a' && value <= 'z'));
+ }
+
+ ///
+ /// Returns true if the path uses the canonical form of extended syntax ("\\?\" or "\??\"). If the
+ /// path matches exactly (cannot use alternate directory separators) Windows will skip normalization
+ /// and path length checks.
+ ///
+ internal static bool IsExtended(ReadOnlySpan path)
+ {
+ // While paths like "//?/C:/" will work, they're treated the same as "\\.\" paths.
+ // Skipping of normalization will *only* occur if back slashes ('\') are used.
+ return path.Length >= DevicePrefixLength
+ && path[0] == '\\'
+ && (path[1] == '\\' || path[1] == '?')
+ && path[2] == '?'
+ && path[3] == '\\';
+ }
+
+ ///
+ /// Returns true if the path ends in a directory separator.
+ ///
+ public static bool DoesEndInDirectorySeparator(ReadOnlySpan path)
+ => path.Length > 0 && PathInternal.IsDirectorySeparator(path[path.Length - 1]);
+
+ ///
+ /// Returns true if the path ends in a directory separator.
+ ///
+ public static bool DoesEndInDirectorySeparator(string path)
+ => path != null && path.Length > 0 && PathInternal.IsDirectorySeparator(path[path.Length - 1]);
+
+ internal const char VolumeSeparatorChar = ':';
+ // \\?\UNC\, \\.\UNC\
+ internal const int UncExtendedPrefixLength = 8;
+ // \\?\, \\.\, \??\
+ internal const int DevicePrefixLength = 4;
+ // \\
+ internal const int UncPrefixLength = 2;
+ // ".."
+ internal const string ParentDir = "..";
+ // '.'
+ internal const char CurrentDir = '.';
+ }
+}
+
diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationBuildTasks/Microsoft.WinFX.targets b/src/Microsoft.DotNet.Wpf/src/PresentationBuildTasks/Microsoft.WinFX.targets
index 3d7878a7c6e..945708d429f 100644
--- a/src/Microsoft.DotNet.Wpf/src/PresentationBuildTasks/Microsoft.WinFX.targets
+++ b/src/Microsoft.DotNet.Wpf/src/PresentationBuildTasks/Microsoft.WinFX.targets
@@ -383,12 +383,14 @@
CleanupTemporaryTargetAssembly
- <_CompileTargetNameForLocalType Condition="'$(_CompileTargetNameForLocalType)' == ''">_CompileTemporaryAssembly
+ <_CompileTargetNameForLocalType Condition="'$(_CompileTargetNameForLocalType)' == ''">_CompileTemporaryAssembly
+ <_ResolveProjectReferencesTargetName Condition="'$(IncludePackageReferencesDuringMarkupCompilation)' != 'false'">ResolveProjectReferences
+ <_CompileTemporaryAssemblyDependsOn>BuildOnlySettings;ResolveKeySource;$(_ResolveProjectReferencesTargetName);CoreCompile
-
+
+
+
+
+
+
+
+ <_ParentProjectName>$([System.IO.Path]::GetFileNameWithoutExtension('$(MSBuildProjectFullPath)'))
+ <_ParentProjectExtension>$([System.IO.Path]::GetExtension($(MSBuildProjectFullPath)))
+ <_TemporaryTargetAssemblyProjectNameNoExtension>$([System.String]::Join("_", "$(_ParentProjectName)", "$([System.IO.Path]::GetFileNameWithoutExtension($([System.IO.Path]::GetRandomFileName())))", "wpftmp"))
+ <_TemporaryTargetAssemblyProjectName>$(_TemporaryTargetAssemblyProjectNameNoExtension)$(_ParentProjectExtension)
+ <_TempProjectNuGetExtensionsPath>$([System.Text.RegularExpressions.Regex]::Replace($(MSBuildProjectExtensionsPath), $(_ParentProjectName), $(_TemporaryTargetAssemblyProjectNameNoExtension), System.Text.RegularExpressions.RegexOptions.IgnoreCase))
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
@@ -434,7 +470,6 @@
-