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..f859bd851e7 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 ($"Removing old symlink: {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)) {