2023-06-08 07:00:41 +00:00
|
|
|
// Licensed to the .NET Foundation under one or more agreements.
|
|
|
|
// The .NET Foundation licenses this file to you under the MIT license.
|
|
|
|
// See the LICENSE file in the project root for more information.
|
|
|
|
|
2023-06-09 09:42:50 +00:00
|
|
|
#if !NETFRAMEWORK
|
|
|
|
#nullable enable
|
|
|
|
|
2023-06-08 07:00:41 +00:00
|
|
|
using System;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.ComponentModel;
|
|
|
|
using System.IO;
|
|
|
|
using System.IO.Enumeration;
|
|
|
|
using System.IO.MemoryMappedFiles;
|
|
|
|
using System.Linq;
|
|
|
|
using System.Runtime.InteropServices;
|
2023-06-09 09:42:50 +00:00
|
|
|
#endif
|
2023-06-08 07:00:41 +00:00
|
|
|
using Microsoft.Build.Framework;
|
|
|
|
using Microsoft.Build.Utilities;
|
|
|
|
|
|
|
|
namespace Microsoft.DotNet.Build.Tasks
|
|
|
|
{
|
|
|
|
/// <summary>
|
|
|
|
/// Replaces files that have the same content with hard links.
|
|
|
|
/// </summary>
|
2023-10-17 08:21:02 +00:00
|
|
|
public sealed class ReplaceFilesWithSymbolicLinks : Task
|
2023-06-08 07:00:41 +00:00
|
|
|
{
|
|
|
|
/// <summary>
|
2023-10-17 08:21:02 +00:00
|
|
|
/// The path to the directory to recursively search for files to replace with symbolic links.
|
2023-06-08 07:00:41 +00:00
|
|
|
/// </summary>
|
|
|
|
[Required]
|
|
|
|
public string Directory { get; set; } = "";
|
|
|
|
|
2023-10-17 08:21:02 +00:00
|
|
|
/// <summary>
|
|
|
|
/// The path to the directory with files to link to.
|
|
|
|
/// </summary>
|
|
|
|
[Required]
|
|
|
|
public string LinkToFilesFrom { get; set; } = "";
|
|
|
|
|
2023-06-09 09:42:50 +00:00
|
|
|
#if NETFRAMEWORK
|
|
|
|
public override bool Execute()
|
|
|
|
{
|
2023-10-17 08:21:02 +00:00
|
|
|
Log.LogError($"{nameof(ReplaceFilesWithSymbolicLinks)} is not supported on .NET Framework.");
|
2023-06-09 09:42:50 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
#else
|
2023-06-08 07:00:41 +00:00
|
|
|
public override bool Execute()
|
|
|
|
{
|
|
|
|
if (OperatingSystem.IsWindows())
|
|
|
|
{
|
2023-10-17 08:21:02 +00:00
|
|
|
Log.LogError($"{nameof(ReplaceFilesWithSymbolicLinks)} is not supported on Windows.");
|
2023-06-08 07:00:41 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!System.IO.Directory.Exists(Directory))
|
|
|
|
{
|
|
|
|
Log.LogError($"'{Directory}' does not exist.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-10-17 08:21:02 +00:00
|
|
|
if (!System.IO.Directory.Exists(LinkToFilesFrom))
|
|
|
|
{
|
|
|
|
Log.LogError($"'{LinkToFilesFrom}' does not exist.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-06-08 07:00:41 +00:00
|
|
|
// Find all non-empty, non-symbolic link files.
|
2023-10-17 08:21:02 +00:00
|
|
|
string[] files = new FileSystemEnumerable<string>(
|
|
|
|
Directory,
|
|
|
|
(ref FileSystemEntry entry) => entry.ToFullPath(),
|
|
|
|
new EnumerationOptions()
|
|
|
|
{
|
|
|
|
AttributesToSkip = FileAttributes.ReparsePoint,
|
|
|
|
RecurseSubdirectories = true
|
|
|
|
})
|
2023-06-08 07:00:41 +00:00
|
|
|
{
|
|
|
|
ShouldIncludePredicate = (ref FileSystemEntry entry) => !entry.IsDirectory
|
|
|
|
&& entry.Length > 0
|
2023-10-17 08:21:02 +00:00
|
|
|
}.ToArray();
|
2023-06-08 07:00:41 +00:00
|
|
|
|
2023-10-17 08:21:02 +00:00
|
|
|
foreach (var file in files)
|
2023-06-08 07:00:41 +00:00
|
|
|
{
|
2023-10-17 08:21:02 +00:00
|
|
|
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))
|
2023-06-08 07:00:41 +00:00
|
|
|
{
|
2023-10-17 08:21:02 +00:00
|
|
|
ReplaceByLinkTo(file, targetFile);
|
2023-06-08 07:00:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
private unsafe bool FilesHaveSameContent(string path1, string path2)
|
|
|
|
{
|
|
|
|
using var mappedFile1 = MemoryMappedFile.CreateFromFile(path1, FileMode.Open);
|
|
|
|
using var accessor1 = mappedFile1.CreateViewAccessor();
|
|
|
|
byte* ptr1 = null;
|
|
|
|
|
|
|
|
using var mappedFile2 = MemoryMappedFile.CreateFromFile(path2, FileMode.Open);
|
|
|
|
using var accessor2 = mappedFile2.CreateViewAccessor();
|
|
|
|
byte* ptr2 = null;
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
accessor1.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr1);
|
|
|
|
Span<byte> span1 = new Span<byte>(ptr1, checked((int)accessor1.SafeMemoryMappedViewHandle.ByteLength));
|
|
|
|
|
|
|
|
accessor2.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr2);
|
|
|
|
Span<byte> span2 = new Span<byte>(ptr2, checked((int)accessor2.SafeMemoryMappedViewHandle.ByteLength));
|
|
|
|
|
|
|
|
return span1.SequenceEqual(span2);
|
|
|
|
}
|
|
|
|
finally
|
|
|
|
{
|
|
|
|
if (ptr1 != null)
|
|
|
|
{
|
|
|
|
accessor1.SafeMemoryMappedViewHandle.ReleasePointer();
|
|
|
|
ptr1 = null;
|
|
|
|
}
|
|
|
|
if (ptr2 != null)
|
|
|
|
{
|
|
|
|
accessor2.SafeMemoryMappedViewHandle.ReleasePointer();
|
|
|
|
ptr2 = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-17 08:21:02 +00:00
|
|
|
void ReplaceByLinkTo(string path, string pathToTarget)
|
2023-06-08 07:00:41 +00:00
|
|
|
{
|
|
|
|
// To link, the target mustn't exist. Make a backup, so we can restore it when linking fails.
|
2023-10-17 08:21:02 +00:00
|
|
|
string backupFile = $"{path}.pre_link_backup";
|
|
|
|
File.Move(path, backupFile);
|
2023-06-08 07:00:41 +00:00
|
|
|
|
2023-10-17 08:21:02 +00:00
|
|
|
try
|
2023-06-08 07:00:41 +00:00
|
|
|
{
|
2023-10-17 08:21:02 +00:00
|
|
|
string relativePath = Path.GetRelativePath(Path.GetDirectoryName(path)!, pathToTarget);
|
|
|
|
File.CreateSymbolicLink(path, relativePath);
|
2023-06-08 07:00:41 +00:00
|
|
|
|
2023-10-17 08:21:02 +00:00
|
|
|
File.Delete(backupFile);
|
2023-06-08 07:00:41 +00:00
|
|
|
|
2023-10-17 08:21:02 +00:00
|
|
|
Log.LogMessage(MessageImportance.Normal, $"Linked '{path}' to '{relativePath}'.");
|
2023-06-08 07:00:41 +00:00
|
|
|
}
|
2023-10-17 08:21:02 +00:00
|
|
|
catch (Exception ex)
|
2023-06-08 07:00:41 +00:00
|
|
|
{
|
2023-10-17 08:21:02 +00:00
|
|
|
Log.LogError($"Unable to link '{path}' to '{pathToTarget}.': {ex}");
|
|
|
|
|
|
|
|
File.Move(backupFile, path);
|
2023-06-08 07:00:41 +00:00
|
|
|
|
2023-10-17 08:21:02 +00:00
|
|
|
throw;
|
2023-06-08 07:00:41 +00:00
|
|
|
}
|
|
|
|
}
|
2023-06-09 09:42:50 +00:00
|
|
|
#endif
|
2023-06-08 07:00:41 +00:00
|
|
|
}
|
|
|
|
}
|