From 1078c97cf81448864ccdbd88f86161ac303f35b0 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Tue, 17 Oct 2023 10:21:02 +0200 Subject: [PATCH] Use symbolic links instead of hard links in runtime pack directories. The hard links are causing issues with package tooling. This changes to use symbolic links instead. Besides fixing the issues with the package tooling, symbolic links are easier to preserve throughout the packaging process. --- ...ks.cs => ReplaceFilesWithSymbolicLinks.cs} | 106 ++++++++---------- src/redist/targets/BuildCoreSdkTasks.targets | 2 +- src/redist/targets/GenerateLayout.targets | 12 +- 3 files changed, 53 insertions(+), 67 deletions(-) rename src/core-sdk-tasks/{ReplaceDuplicateFilesWithHardLinks.cs => ReplaceFilesWithSymbolicLinks.cs} (50%) diff --git a/src/core-sdk-tasks/ReplaceDuplicateFilesWithHardLinks.cs b/src/core-sdk-tasks/ReplaceFilesWithSymbolicLinks.cs similarity index 50% rename from src/core-sdk-tasks/ReplaceDuplicateFilesWithHardLinks.cs rename to src/core-sdk-tasks/ReplaceFilesWithSymbolicLinks.cs index 7fe6559fc..44c6ca31b 100644 --- a/src/core-sdk-tasks/ReplaceDuplicateFilesWithHardLinks.cs +++ b/src/core-sdk-tasks/ReplaceFilesWithSymbolicLinks.cs @@ -22,18 +22,24 @@ namespace Microsoft.DotNet.Build.Tasks /// /// Replaces files that have the same content with hard links. /// - public sealed class ReplaceDuplicateFilesWithHardLinks : Task + public sealed class ReplaceFilesWithSymbolicLinks : Task { /// - /// The path to the directory. + /// The path to the directory to recursively search for files to replace with symbolic links. /// [Required] public string Directory { get; set; } = ""; + /// + /// The path to the directory with files to link to. + /// + [Required] + public string LinkToFilesFrom { get; set; } = ""; + #if NETFRAMEWORK public override bool Execute() { - Log.LogError($"{nameof(ReplaceDuplicateFilesWithHardLinks)} is not supported on .NET Framework."); + Log.LogError($"{nameof(ReplaceFilesWithSymbolicLinks)} is not supported on .NET Framework."); return false; } #else @@ -41,7 +47,7 @@ namespace Microsoft.DotNet.Build.Tasks { if (OperatingSystem.IsWindows()) { - Log.LogError($"{nameof(ReplaceDuplicateFilesWithHardLinks)} is not supported on Windows."); + Log.LogError($"{nameof(ReplaceFilesWithSymbolicLinks)} is not supported on Windows."); return false; } @@ -51,52 +57,36 @@ namespace Microsoft.DotNet.Build.Tasks return false; } + if (!System.IO.Directory.Exists(LinkToFilesFrom)) + { + Log.LogError($"'{LinkToFilesFrom}' does not exist."); + return false; + } + // Find all non-empty, non-symbolic link files. - IEnumerable fse = new FileSystemEnumerable( - Directory, - (ref FileSystemEntry entry) => (FileInfo)entry.ToFileSystemInfo(), - new EnumerationOptions() - { - AttributesToSkip = FileAttributes.ReparsePoint, - RecurseSubdirectories = true - }) + string[] files = new FileSystemEnumerable( + Directory, + (ref FileSystemEntry entry) => entry.ToFullPath(), + new EnumerationOptions() + { + AttributesToSkip = FileAttributes.ReparsePoint, + RecurseSubdirectories = true + }) { ShouldIncludePredicate = (ref FileSystemEntry entry) => !entry.IsDirectory && entry.Length > 0 - }; + }.ToArray(); - // Group them by file size. - IEnumerable filesGroupedBySize = fse.GroupBy(file => file.Length, - file => file.FullName, - (size, files) => files.ToArray()); - - // Replace files with same content with hard link. - foreach (var files in filesGroupedBySize) + foreach (var file in files) { - for (int i = 0; i < files.Length; i++) + string fileName = Path.GetFileName(file); + + // Look for a file with the same name in LinkToFilesFrom + // and replace it with a symbolic link if it has the same content. + string targetFile = Path.Combine(LinkToFilesFrom, fileName); + if (File.Exists(targetFile) && FilesHaveSameContent(file, targetFile)) { - string? path1 = files[i]; - if (path1 is null) - { - continue; // already linked. - } - for (int j = i + 1; j < files.Length; j++) - { - string? path2 = files[j]; - if (path2 is null) - { - continue; // already linked. - } - - // note: There's no public API we can use to see if paths are already linked. - // We treat those paths as unlinked files, and link them again. - if (FilesHaveSameContent(path1, path2)) - { - ReplaceByLink(path1, path2); - - files[j] = null; - } - } + ReplaceByLinkTo(file, targetFile); } } @@ -138,34 +128,30 @@ namespace Microsoft.DotNet.Build.Tasks } } - void ReplaceByLink(string path1, string path2) + void ReplaceByLinkTo(string path, string pathToTarget) { // To link, the target mustn't exist. Make a backup, so we can restore it when linking fails. - string path2Backup = $"{path2}.pre_link_backup"; - File.Move(path2, path2Backup); + string backupFile = $"{path}.pre_link_backup"; + File.Move(path, backupFile); - int rv = SystemNative_Link(path1, path2); - if (rv != 0) + try { - var ex = new Win32Exception(); // Captures the LastError. + string relativePath = Path.GetRelativePath(Path.GetDirectoryName(path)!, pathToTarget); + File.CreateSymbolicLink(path, relativePath); - Log.LogError($"Unable to link '{path2}' to '{path1}.': {ex}"); + File.Delete(backupFile); - File.Move(path2Backup, path2); - - throw ex; + Log.LogMessage(MessageImportance.Normal, $"Linked '{path}' to '{relativePath}'."); } - else + catch (Exception ex) { - File.Delete(path2Backup); + Log.LogError($"Unable to link '{path}' to '{pathToTarget}.': {ex}"); - Log.LogMessage(MessageImportance.Normal, $"Linked '{path1}' and '{path2}'."); + File.Move(backupFile, path); + + throw; } } - - // This native method is used by the runtime to create hard links. It is not exposed through a public .NET API. - [DllImport("libSystem.Native", SetLastError = true)] - static extern int SystemNative_Link(string source, string link); #endif } } diff --git a/src/redist/targets/BuildCoreSdkTasks.targets b/src/redist/targets/BuildCoreSdkTasks.targets index afd5d21bd..d28d0aee1 100644 --- a/src/redist/targets/BuildCoreSdkTasks.targets +++ b/src/redist/targets/BuildCoreSdkTasks.targets @@ -40,6 +40,6 @@ - + diff --git a/src/redist/targets/GenerateLayout.targets b/src/redist/targets/GenerateLayout.targets index 899b46f78..94ef6ba36 100644 --- a/src/redist/targets/GenerateLayout.targets +++ b/src/redist/targets/GenerateLayout.targets @@ -569,11 +569,11 @@ - - - + + + +