Skip to content

Commit c062fee

Browse files
authored
Use hardlinks during local publishing if possible (#46161)
1 parent 76347bf commit c062fee

File tree

1 file changed

+168
-0
lines changed

1 file changed

+168
-0
lines changed
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
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

Comments
 (0)