From 6f268672dcb6db8f8333c91fb0254495135f8728 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Thu, 31 Oct 2019 13:53:02 -0400 Subject: [PATCH] [xabuild] Find Microsoft.Portable.CSharp.targets Builds on Windows were failing because `Microsoft.Portable.CSharp.targets` couldn't be found: > bin\Debug\bin\xabuild /bl Xamarin.Android-Tests.sln ... ...\xamarin-android\external\Java.Interop\lib\mono.linq.expressions\Mono.Linq.Expressions.csproj(108,3): error MSB4019: The imported project "...\xamarin-android\bin\Debug\lib\xamarin.android\xbuild\Microsoft\Portable\v4.0\Microsoft.Portable.CSharp.targets" was not found. Confirm that the path in the declaration is correct, and that the file exists on disk. Turns Out that there are *multiple* `Microsoft` directories of consequence, and we need *all* of them to build, including: * `%ProgramFiles(x86)%\Microsoft Visual Studio\2019\*\MSBuild\Microsoft` * `%ProgramFiles(x86)%\MSBuild\Microsoft\Portable` `xabuild.exe` would symlink the first one into `bin\$(Configuration)\lib\xamarin.android\xbuild`, but not the latter, and lack of the `Microsoft\Portable` directory prevented `Microsoft.Portable.CSharp.targets` from being found. Update `xabuild.exe` to know about `%ProgramFiles(x86)%\MSBuild\Microsoft`. Furthermore, because we now have two `Microsoft` directories, `Microsoft` must now become a "normal" directory, which contains symlinks to *all* the known child directories. To support this, "invert" the `symbolicLinks` dictionary so that the keys are the in-tree paths, making it easier to detect when we need to do such merges. Finally, add code to detect if an existing "conflicting" directory (such as `Microsoft`) is a symlink, and remove it if necessary. This should allow `git pull` + rebuild scenarios to work. --- tools/xabuild/SymbolicLink.cs | 108 ++++++++++++++++++++++++++++++++++ tools/xabuild/XABuild.cs | 29 +++++++-- tools/xabuild/XABuildPaths.cs | 6 +- 3 files changed, 136 insertions(+), 7 deletions(-) diff --git a/tools/xabuild/SymbolicLink.cs b/tools/xabuild/SymbolicLink.cs index a743c933c41..7531e912cf6 100644 --- a/tools/xabuild/SymbolicLink.cs +++ b/tools/xabuild/SymbolicLink.cs @@ -10,6 +10,7 @@ static class SymbolicLink public static bool Create (string source, string target) { if (!Directory.Exists (source)) { + Directory.CreateDirectory (Path.GetDirectoryName (source)); if (Environment.OSVersion.Platform == PlatformID.Win32NT) { return CreateWindowsSymLink (source, target); } else { @@ -50,6 +51,74 @@ enum SymbolLinkFlag { AllowUnprivilegedCreate = 2, } + public static string GetRealPath (string path) + { + if (string.IsNullOrEmpty (path)) + return null; + + if (Environment.OSVersion.Platform == PlatformID.Win32NT) { + return GetWindowsRealPath (path); + } else { + return GetUnixRealPath (path); + } + } + + static string GetWindowsRealPath (string path) + { + const FileAttributes FILE_FLAG_BACKUP_SEMANTICS = (FileAttributes) 0x02000000; + const FileAccess GENERIC_READ = unchecked((FileAccess) 0x80000000); + IntPtr handle = CreateFileW (lpFileName: path, + dwDesiredAccess: GENERIC_READ, + dwShareMode: FileShare.Read, + lpSecurityAttributes: IntPtr.Zero, + dwCreationDisposition: FileMode.Open, + dwFlagsAndAttributes: FILE_FLAG_BACKUP_SEMANTICS, + hTemplateFile: IntPtr.Zero); + if (handle == INVALID_FILE_HANDLE) + return null; + IntPtr finalPathBuf = IntPtr.Zero; + try { + const FinalPathFlags flags = FinalPathFlags.FILE_NAME_OPENED; + uint len = GetFinalPathNameByHandleW (handle, IntPtr.Zero, 0, flags); + if (len == 0) + return null; + len = checked(len + 1); + finalPathBuf = Marshal.AllocHGlobal (checked ((int) (sizeof (char)*(len)))); + uint checkLen = GetFinalPathNameByHandleW (handle, finalPathBuf, len, flags); + if (checkLen == 0 || checkLen > len) { + Console.Error.WriteLine ($"GetFinalPathNameByHandleW: expected {len}, got {checkLen}. Last Error: {Marshal.GetLastWin32Error()}"); + return null; + } + const string LocalUncPathPrefix = @"\\?\"; + string finalPath = Marshal.PtrToStringUni (finalPathBuf); + if (finalPath?.StartsWith (LocalUncPathPrefix, StringComparison.Ordinal) ?? false) + finalPath = finalPath.Substring (LocalUncPathPrefix.Length); + return finalPath; + } + finally { + Marshal.FreeHGlobal (finalPathBuf); + CloseHandle (handle); + } + } + + static string GetUnixRealPath (string path) + { + IntPtr buf = realpath (path, IntPtr.Zero); + try { + if (buf == IntPtr.Zero) + return null; + return Marshal.PtrToStringAnsi (buf); + } + finally { + free (buf); + } + } + + public static bool IsPathSymlink (string path) + { + return Path.GetFullPath (path) != GetRealPath (path); + } + [DllImport ("kernel32.dll")] [return: MarshalAs (UnmanagedType.I1)] static extern bool CreateSymbolicLink (string lpSymlinkFileName, string lpTargetFileName, SymbolLinkFlag dwFlags); @@ -59,5 +128,44 @@ enum SymbolLinkFlag { [DllImport ("libc")] static extern void perror (string s); + + [DllImport ("libc")] + static extern IntPtr realpath (string file_name, IntPtr resolved_name); + + [DllImport ("libc")] + static extern void free (IntPtr p); + + static readonly IntPtr INVALID_FILE_HANDLE = new IntPtr (-1); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern IntPtr CreateFileW( + [MarshalAs(UnmanagedType.LPWStr)] string lpFileName, + [MarshalAs(UnmanagedType.U4)] FileAccess dwDesiredAccess, + [MarshalAs(UnmanagedType.U4)] FileShare dwShareMode, + IntPtr lpSecurityAttributes, + [MarshalAs(UnmanagedType.U4)] FileMode dwCreationDisposition, + [MarshalAs(UnmanagedType.U4)] FileAttributes dwFlagsAndAttributes, + IntPtr hTemplateFile); + + [DllImport("kernel32.dll", SetLastError=true)] + [return: MarshalAs(UnmanagedType.Bool)] + static extern bool CloseHandle(IntPtr hObject); + + [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + static extern uint GetFinalPathNameByHandleW( + IntPtr hFile, + IntPtr lpszFilePath, + uint cchFilePath, + FinalPathFlags dwFlags); + } + + [Flags] + enum FinalPathFlags : uint { + VOLUME_NAME_DOS = 0x0, + FILE_NAME_NORMALIZED = 0x0, + VOLUME_NAME_GUID = 0x1, + VOLUME_NAME_NT = 0x2, + VOLUME_NAME_NONE = 0x4, + FILE_NAME_OPENED = 0x8, } } diff --git a/tools/xabuild/XABuild.cs b/tools/xabuild/XABuild.cs index 83946bf8002..b4653ca1077 100644 --- a/tools/xabuild/XABuild.cs +++ b/tools/xabuild/XABuild.cs @@ -38,22 +38,39 @@ static int Main () // Create a Microsoft.Build.NuGetSdkResolver.xml CreateSdkResolverConfig (paths); - //Symbolic links to be created: key=system, value=in-tree + //Symbolic links to be created: key=in-tree-dir, value=system-dir var symbolicLinks = new Dictionary (); foreach (var dir in Directory.EnumerateDirectories (paths.SystemFrameworks)) { if (Path.GetFileName (dir) != "MonoAndroid") { - symbolicLinks [dir] = Path.Combine (paths.FrameworksDirectory, Path.GetFileName (dir)); + var inTreeFramework = Path.Combine (paths.FrameworksDirectory, Path.GetFileName (dir)); + symbolicLinks [inTreeFramework] = dir; } } foreach (var dir in paths.SystemTargetsDirectories) { - symbolicLinks [dir] = Path.Combine (paths.MSBuildExtensionsPath, Path.GetFileName (dir)); + var inTreeTargetsDir = Path.Combine (paths.MSBuildExtensionsPath, Path.GetFileName (dir)); + if (!symbolicLinks.ContainsKey (inTreeTargetsDir)) { + symbolicLinks [inTreeTargetsDir] = dir; + continue; + } + var prevTargetDir = symbolicLinks [inTreeTargetsDir]; + symbolicLinks.Remove (inTreeTargetsDir); + if (Directory.Exists (inTreeTargetsDir) && SymbolicLink.IsPathSymlink (inTreeTargetsDir)) { + Console.WriteLine ($"# jonp: Removing path? {inTreeTargetsDir}"); + Directory.Delete (inTreeTargetsDir); + } + var subTargetDirs = Directory.EnumerateDirectories (prevTargetDir) + .Concat (Directory.EnumerateDirectories (dir)); + foreach (var subDir in subTargetDirs) { + var inTreeTargetSubdir = Path.Combine (inTreeTargetsDir, Path.GetFileName (subDir)); + symbolicLinks [inTreeTargetSubdir] = subDir; + } } - if (symbolicLinks.Values.Any (d => !Directory.Exists (d))) { + if (symbolicLinks.Keys.Any (d => !Directory.Exists (d))) { //Hold open the file while creating the symbolic links using (var writer = OpenSysLinksFile (paths)) { foreach (var pair in symbolicLinks) { - var systemDirectory = pair.Key; - var symbolicLink = pair.Value; + var systemDirectory = pair.Value; + var symbolicLink = pair.Key; Console.WriteLine ($"[xabuild] creating symbolic link '{symbolicLink}' -> '{systemDirectory}'"); if (!SymbolicLink.Create (symbolicLink, systemDirectory)) { return 1; diff --git a/tools/xabuild/XABuildPaths.cs b/tools/xabuild/XABuildPaths.cs index 717ea4b243a..f367cdb29bf 100644 --- a/tools/xabuild/XABuildPaths.cs +++ b/tools/xabuild/XABuildPaths.cs @@ -147,7 +147,11 @@ public XABuildPaths () MSBuildSdksPath = DotNetSdkPath ?? Path.Combine (MSBuildPath, "Sdks"); SystemFrameworks = Path.Combine (programFiles, "Reference Assemblies", "Microsoft", "Framework"); string msbuildDir = Path.GetDirectoryName (MSBuildBin); - SystemTargetsDirectories = new [] { msbuildDir, Path.Combine (MSBuildPath, "Microsoft") }; + SystemTargetsDirectories = new [] { + msbuildDir, + Path.Combine (MSBuildPath, "Microsoft"), + Path.Combine (programFiles, "MSBuild", "Microsoft"), + }; SearchPathsOS = "windows"; string nuget = Path.Combine (MSBuildPath, "Microsoft", "NuGet", "16.0"); if (!Directory.Exists (nuget)) {