|
| 1 | +From 497d1fdfb20324e9a9fc41a8d7d19f50d5b32a5c Mon Sep 17 00:00:00 2001 |
| 2 | +From: Viktor Hofer < [email protected]> |
| 3 | +Date: Tue, 21 Jan 2025 10:50:44 +0100 |
| 4 | +Subject: [PATCH] Use hardlinks during local file publishing if possible |
| 5 | + |
| 6 | +Backport: https://github.com/dotnet/arcade/pull/15433 |
| 7 | + |
| 8 | +--- |
| 9 | + .../tools/Publish.proj | 3 +- |
| 10 | + .../Microsoft.DotNet.Build.Tasks.Feed.csproj | 1 + |
| 11 | + .../src/PushToBuildStorage.cs | 32 +++++++++++-- |
| 12 | + .../src/common/NativeMethods.cs | 48 +++++++++++++++++++ |
| 13 | + 4 files changed, 79 insertions(+), 5 deletions(-) |
| 14 | + create mode 100644 src/Microsoft.DotNet.Build.Tasks.Feed/src/common/NativeMethods.cs |
| 15 | + |
| 16 | +diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/Publish.proj b/src/Microsoft.DotNet.Arcade.Sdk/tools/Publish.proj |
| 17 | +index 907d3d876..3b3e4d98e 100644 |
| 18 | +--- a/src/Microsoft.DotNet.Arcade.Sdk/tools/Publish.proj |
| 19 | ++++ b/src/Microsoft.DotNet.Arcade.Sdk/tools/Publish.proj |
| 20 | +@@ -274,7 +274,8 @@ |
| 21 | + ShippingPackagesLocalStorageDir="$(SourceBuiltShippingPackagesDir)" |
| 22 | + NonShippingPackagesLocalStorageDir="$(SourceBuiltNonShippingPackagesDir)" |
| 23 | + AssetManifestsLocalStorageDir="$(SourceBuiltAssetManifestsDir)" |
| 24 | +- ArtifactVisibilitiesToPublish="@(ArtifactVisibilityToPublish)" /> |
| 25 | ++ ArtifactVisibilitiesToPublish="@(ArtifactVisibilityToPublish)" |
| 26 | ++ UseHardlinksIfPossible="$(PublishingUseHardlinksIfPossible)" /> |
| 27 | + |
| 28 | + <!-- |
| 29 | + Publish Windows PDBs produced by SymStore.targets (by default, only shipping PDBs are placed there). |
| 30 | +diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed/Microsoft.DotNet.Build.Tasks.Feed.csproj b/src/Microsoft.DotNet.Build.Tasks.Feed/Microsoft.DotNet.Build.Tasks.Feed.csproj |
| 31 | +index b3b027c33..224b7b645 100644 |
| 32 | +--- a/src/Microsoft.DotNet.Build.Tasks.Feed/Microsoft.DotNet.Build.Tasks.Feed.csproj |
| 33 | ++++ b/src/Microsoft.DotNet.Build.Tasks.Feed/Microsoft.DotNet.Build.Tasks.Feed.csproj |
| 34 | +@@ -2,6 +2,7 @@ |
| 35 | + |
| 36 | + <PropertyGroup> |
| 37 | + <TargetFrameworks>$(NetToolCurrent);$(NetFrameworkToolCurrent)</TargetFrameworks> |
| 38 | ++ <AllowUnsafeBlocks>true</AllowUnsafeBlocks> |
| 39 | + <SignAssembly>true</SignAssembly> |
| 40 | + <Description>This package provides support for publishing assets to appropriate channels.</Description> |
| 41 | + <DevelopmentDependency>true</DevelopmentDependency> |
| 42 | +diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed/src/PushToBuildStorage.cs b/src/Microsoft.DotNet.Build.Tasks.Feed/src/PushToBuildStorage.cs |
| 43 | +index c506295dc..7e4cd385c 100644 |
| 44 | +--- a/src/Microsoft.DotNet.Build.Tasks.Feed/src/PushToBuildStorage.cs |
| 45 | ++++ b/src/Microsoft.DotNet.Build.Tasks.Feed/src/PushToBuildStorage.cs |
| 46 | +@@ -82,6 +82,12 @@ public class PushToBuildStorage : MSBuildTaskBase |
| 47 | + /// </summary> |
| 48 | + public string PublishingVersion { get; set; } |
| 49 | + |
| 50 | ++ /// <summary> |
| 51 | ++ /// Gets or sets a value that indicates whether to use hard links for the copied files |
| 52 | ++ /// rather than copy the files, if it's possible to do so. |
| 53 | ++ /// </summary> |
| 54 | ++ public bool UseHardlinksIfPossible { get; set; } = true; |
| 55 | ++ |
| 56 | + public enum ItemType |
| 57 | + { |
| 58 | + AssetManifest = 0, |
| 59 | +@@ -268,19 +274,19 @@ private void PushToLocalStorageOrAzDO(ItemType itemType, ITaskItem item) |
| 60 | + { |
| 61 | + case ItemType.AssetManifest: |
| 62 | + Directory.CreateDirectory(AssetManifestsLocalStorageDir); |
| 63 | +- File.Copy(path, Path.Combine(AssetManifestsLocalStorageDir, filename), true); |
| 64 | ++ CopyFileAsHardLinkIfPossible(path, Path.Combine(AssetManifestsLocalStorageDir, filename), true); |
| 65 | + break; |
| 66 | + |
| 67 | + case ItemType.PackageArtifact: |
| 68 | + if (string.Equals(item.GetMetadata("IsShipping"), "true", StringComparison.OrdinalIgnoreCase)) |
| 69 | + { |
| 70 | + Directory.CreateDirectory(ShippingPackagesLocalStorageDir); |
| 71 | +- File.Copy(path, Path.Combine(ShippingPackagesLocalStorageDir, filename), true); |
| 72 | ++ CopyFileAsHardLinkIfPossible(path, Path.Combine(ShippingPackagesLocalStorageDir, filename), true); |
| 73 | + } |
| 74 | + else |
| 75 | + { |
| 76 | + Directory.CreateDirectory(NonShippingPackagesLocalStorageDir); |
| 77 | +- File.Copy(path, Path.Combine(NonShippingPackagesLocalStorageDir, filename), true); |
| 78 | ++ CopyFileAsHardLinkIfPossible(path, Path.Combine(NonShippingPackagesLocalStorageDir, filename), true); |
| 79 | + } |
| 80 | + break; |
| 81 | + |
| 82 | +@@ -291,7 +297,7 @@ private void PushToLocalStorageOrAzDO(ItemType itemType, ITaskItem item) |
| 83 | + string.IsNullOrEmpty(relativeBlobPath) ? filename : relativeBlobPath); |
| 84 | + |
| 85 | + Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)); |
| 86 | +- File.Copy(path, destinationPath, true); |
| 87 | ++ CopyFileAsHardLinkIfPossible(path, destinationPath, true); |
| 88 | + break; |
| 89 | + |
| 90 | + default: |
| 91 | +@@ -334,5 +340,23 @@ private static ArtifactVisibility[] GetVisibilitiesToPublish(ITaskItem[] allowed |
| 92 | + |
| 93 | + return allowedVisibilities.Select(item => (ArtifactVisibility)Enum.Parse(typeof(ArtifactVisibility), item.ItemSpec)).ToArray(); |
| 94 | + } |
| 95 | ++ |
| 96 | ++ private void CopyFileAsHardLinkIfPossible(string sourceFileName, string destFileName, bool overwrite) |
| 97 | ++ { |
| 98 | ++ if (UseHardlinksIfPossible) |
| 99 | ++ { |
| 100 | ++ Log.LogMessage(MessageImportance.Normal, "Creating hard link to copy \"{0}\" to \"{1}\".", sourceFileName, destFileName); |
| 101 | ++ |
| 102 | ++ string errorMessage = string.Empty; |
| 103 | ++ if (NativeMethods.MakeHardLink(destFileName, sourceFileName, ref errorMessage)) |
| 104 | ++ { |
| 105 | ++ return; |
| 106 | ++ } |
| 107 | ++ |
| 108 | ++ Log.LogMessage(MessageImportance.Normal, "Could not use a link to copy \"{0}\" to \"{1}\". Copying the file instead. {2}", sourceFileName, destFileName, errorMessage); |
| 109 | ++ } |
| 110 | ++ |
| 111 | ++ File.Copy(sourceFileName, destFileName, overwrite); |
| 112 | ++ } |
| 113 | + } |
| 114 | + } |
| 115 | +diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed/src/common/NativeMethods.cs b/src/Microsoft.DotNet.Build.Tasks.Feed/src/common/NativeMethods.cs |
| 116 | +new file mode 100644 |
| 117 | +index 000000000..7eaa76db7 |
| 118 | +--- /dev/null |
| 119 | ++++ b/src/Microsoft.DotNet.Build.Tasks.Feed/src/common/NativeMethods.cs |
| 120 | +@@ -0,0 +1,48 @@ |
| 121 | ++// Licensed to the .NET Foundation under one or more agreements. |
| 122 | ++// The .NET Foundation licenses this file to you under the MIT license. |
| 123 | ++ |
| 124 | ++using System; |
| 125 | ++using System.Runtime.InteropServices; |
| 126 | ++#if NET |
| 127 | ++using System.Runtime.InteropServices.Marshalling; |
| 128 | ++#endif |
| 129 | ++ |
| 130 | ++namespace Microsoft.DotNet.Build.Tasks.Feed |
| 131 | ++{ |
| 132 | ++ internal partial class NativeMethods |
| 133 | ++ { |
| 134 | ++#if NET |
| 135 | ++ [LibraryImport("kernel32.dll", EntryPoint = "CreateHardLinkW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)] |
| 136 | ++ [return: MarshalAs(UnmanagedType.Bool)] |
| 137 | ++ internal static partial bool CreateHardLink(string newFileName, string exitingFileName, IntPtr securityAttributes); |
| 138 | ++#else |
| 139 | ++ [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] |
| 140 | ++ internal static extern bool CreateHardLink(string newFileName, string exitingFileName, IntPtr securityAttributes); |
| 141 | ++#endif |
| 142 | ++ |
| 143 | ++#if NET |
| 144 | ++ [LibraryImport("libc", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)] |
| 145 | ++ internal static partial int link(string oldpath, string newpath); |
| 146 | ++#else |
| 147 | ++ [DllImport("libc", SetLastError = true)] |
| 148 | ++ internal static extern int link(string oldpath, string newpath); |
| 149 | ++#endif |
| 150 | ++ |
| 151 | ++ internal static bool MakeHardLink(string newFileName, string exitingFileName, ref string errorMessage) |
| 152 | ++ { |
| 153 | ++ bool hardLinkCreated; |
| 154 | ++ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) |
| 155 | ++ { |
| 156 | ++ hardLinkCreated = CreateHardLink(newFileName, exitingFileName, IntPtr.Zero /* reserved, must be NULL */); |
| 157 | ++ errorMessage = hardLinkCreated ? null : Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()).Message; |
| 158 | ++ } |
| 159 | ++ else |
| 160 | ++ { |
| 161 | ++ hardLinkCreated = link(exitingFileName, newFileName) == 0; |
| 162 | ++ errorMessage = hardLinkCreated ? null : $"The link() library call failed with the following error code: {Marshal.GetLastWin32Error()}."; |
| 163 | ++ } |
| 164 | ++ |
| 165 | ++ return hardLinkCreated; |
| 166 | ++ } |
| 167 | ++ } |
| 168 | ++} |
0 commit comments