Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Copy task SourceFolders #8843

Merged
merged 42 commits into from
Dec 14, 2023
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
3703d51
add SourceFolders parameter property
jrdodds May 11, 2023
fbe6b51
Merge branch 'dotnet:main' into CopyTask-SourceFolders
jrdodds May 12, 2023
906aa4e
add CopyParameter_Tests
jrdodds May 15, 2023
7c946a8
reorganize Copy_Tests to eliminate duplicate test runs
jrdodds May 16, 2023
81a2007
move static methods within class and add/modify comments
jrdodds May 16, 2023
6f60ad8
Merge branch 'dotnet:main' into CopyTask-SourceFolders
jrdodds May 17, 2023
85c8afa
Merge branch 'CopyTask-UnitTest' into CopyTask-SourceFolders
jrdodds May 17, 2023
7804687
move methods to Copy_Tests; remove CopyParameter_Tests class
jrdodds May 17, 2023
0ff4526
Merge branch 'dotnet:main' into CopyTask-SourceFolders
jrdodds May 21, 2023
7b35c74
chnage input validation; add unit tests for input validation
jrdodds May 31, 2023
7dc0d67
Merge branch 'dotnet:main' into CopyTask-SourceFolders
jrdodds Jun 2, 2023
8c8abf8
implement SourceFolders
jrdodds Jun 5, 2023
717b97b
remove TODO and modify call to GetFiles()
jrdodds Jun 6, 2023
0e7ed6b
changed test CopyWithSourceFoldersToDestinationFolder
jrdodds Jun 6, 2023
c8db782
use InvariantCulture with double.TryParse
jrdodds Jun 7, 2023
e41fef4
Revert "use InvariantCulture with double.TryParse"
jrdodds Jun 7, 2023
77b42d7
Merge branch 'dotnet:main' into CopyTask-SourceFolders
jrdodds Jun 7, 2023
f46b5e6
Merge branch 'dotnet:main' into CopyTask-SourceFolders
jrdodds Jun 27, 2023
ab41e1b
Merge branch 'dotnet:main' into CopyTask-SourceFolders
jrdodds Jun 28, 2023
243de43
Merge branch 'dotnet:main' into CopyTask-SourceFolders
jrdodds Jun 30, 2023
62cbc6a
Merge branch 'dotnet:main' into CopyTask-SourceFolders
jrdodds Jul 6, 2023
343e0b3
Merge branch 'dotnet:main' into CopyTask-SourceFolders
jrdodds Aug 13, 2023
6a5c1aa
Merge branch 'dotnet:main' into CopyTask-SourceFolders
jrdodds Aug 30, 2023
fc932c4
Merge branch 'dotnet:main' into CopyTask-SourceFolders
jrdodds Sep 13, 2023
8c8347d
update error code for Copy.IncompatibleParameters
jrdodds Sep 26, 2023
3d72c84
Merge branch 'main' into CopyTask-SourceFolders
jrdodds Sep 26, 2023
4d33e04
updated Strings
jrdodds Sep 26, 2023
27b25f6
Merge branch 'dotnet:main' into CopyTask-SourceFolders
jrdodds Oct 2, 2023
1cc07fd
Merge branch 'dotnet:main' into CopyTask-SourceFolders
jrdodds Oct 5, 2023
ab3a727
Merge branch 'dotnet:main' into CopyTask-SourceFolders
jrdodds Oct 10, 2023
7c3d6b2
Merge branch 'dotnet:main' into CopyTask-SourceFolders
jrdodds Oct 23, 2023
eb59f92
Merge branch 'dotnet:main' into CopyTask-SourceFolders
jrdodds Oct 26, 2023
41e0bb3
change to make the empty set for source an error
jrdodds Oct 27, 2023
7348a43
CopyWithNoInput should not assume validation order
jrdodds Oct 27, 2023
0a27056
Merge branch 'main' into CopyTask-SourceFolders
jrdodds Nov 7, 2023
896e9ee
revise to allow empty set
jrdodds Nov 9, 2023
d92e61b
Merge branch 'dotnet:main' into CopyTask-SourceFolders
jrdodds Nov 9, 2023
f9b9a6b
test DestinationFiles in CopyWithNoInput test; handle empty destinati…
jrdodds Nov 9, 2023
dfdc428
Merge branch 'dotnet:main' into CopyTask-SourceFolders
jrdodds Nov 17, 2023
8a57726
Merge branch 'dotnet:main' into CopyTask-SourceFolders
jrdodds Nov 19, 2023
ae2d5af
Merge branch 'dotnet:main' into CopyTask-SourceFolders
jrdodds Dec 1, 2023
e8aefea
Merge branch 'dotnet:main' into CopyTask-SourceFolders
jrdodds Dec 8, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
391 changes: 390 additions & 1 deletion src/Tasks.UnitTests/Copy_Tests.cs

Large diffs are not rendered by default.

147 changes: 123 additions & 24 deletions src/Tasks/Copy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks.Dataflow;

using Microsoft.Build.Eventing;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
Expand Down Expand Up @@ -99,9 +100,10 @@ public Copy()
/// </summary>
private const int RetryDelayMillisecondsDefault = 1000;

[Required]
public ITaskItem[] SourceFiles { get; set; }

public ITaskItem[] SourceFolders { get; set; }

public ITaskItem DestinationFolder { get; set; }

/// <summary>
Expand Down Expand Up @@ -402,7 +404,7 @@ private void MakeFileWriteable(FileState file, bool logActivity)
int parallelism)
{
// If there are no source files then just return success.
if (SourceFiles == null || SourceFiles.Length == 0)
if (IsSourceSetEmpty())
{
DestinationFiles = Array.Empty<ITaskItem>();
CopiedFiles = Array.Empty<ITaskItem>();
Expand Down Expand Up @@ -643,6 +645,11 @@ private void MakeFileWriteable(FileState file, bool logActivity)
return success;
}

private bool IsSourceSetEmpty()
{
return (SourceFiles == null || SourceFiles.Length == 0) && (SourceFolders == null || SourceFolders.Length == 0);
}

/// <summary>
/// Verify that the inputs are correct.
/// </summary>
Expand All @@ -661,7 +668,7 @@ private bool ValidateInputs()
return false;
}

// There must be a destinationFolder (either files or directory).
// There must be a destination (either files or directory).
if (DestinationFiles == null && DestinationFolder == null)
{
Log.LogErrorWithCodeFromResources("Copy.NeedsDestination", "DestinationFiles", "DestinationFolder");
Expand All @@ -675,14 +682,20 @@ private bool ValidateInputs()
return false;
}

// SourceFolders and DestinationFiles can't be used together.
if (SourceFolders != null && DestinationFiles != null)
{
Log.LogErrorWithCodeFromResources("Copy.IncompatibleParameters", "SourceFolders", "DestinationFiles");
return false;
}

// If the caller passed in DestinationFiles, then its length must match SourceFiles.
if (DestinationFiles != null && DestinationFiles.Length != SourceFiles.Length)
{
Log.LogErrorWithCodeFromResources("General.TwoVectorsMustHaveSameLength", DestinationFiles.Length, SourceFiles.Length, "DestinationFiles", "SourceFiles");
return false;
}


if (ErrorIfLinkFails && !UseHardlinksIfPossible && !UseSymboliclinksIfPossible)
{
Log.LogErrorWithCodeFromResources("Copy.ErrorIfLinkFailsSetWithoutLinkOption");
Expand All @@ -694,41 +707,127 @@ private bool ValidateInputs()

/// <summary>
/// Set up our list of destination files.
/// For SourceFiles: Apply DestinationFolder to each SourceFiles item to create a DestinationFiles item.
/// For SourceFolders: With each SourceFolders item, get the files in the represented directory. Create both SourceFiles and DestinationFiles items.
/// </summary>
/// <returns>False if an error occurred, implying aborting the overall copy operation.</returns>
private bool InitializeDestinationFiles()
{
if (DestinationFiles == null)
bool isSuccess = true;

try
{
// If the caller passed in DestinationFolder, convert it to DestinationFiles
DestinationFiles = new ITaskItem[SourceFiles.Length];

for (int i = 0; i < SourceFiles.Length; ++i)
if (DestinationFiles == null && SourceFiles != null)
{
// Build the correct path.
string destinationFile;
try
DestinationFiles = new ITaskItem[SourceFiles.Length];

for (int i = 0; i < SourceFiles.Length; ++i)
{
destinationFile = Path.Combine(DestinationFolder.ItemSpec, Path.GetFileName(SourceFiles[i].ItemSpec));
// Build the correct path.
if (!TryPathOperation(
() => Path.Combine(DestinationFolder.ItemSpec, Path.GetFileName(SourceFiles[i].ItemSpec)),
SourceFiles[i].ItemSpec,
DestinationFolder.ItemSpec,
out string destinationFile))
{
isSuccess = false;
break;
}

// Initialize the destinationFolder item.
// ItemSpec is unescaped, and the TaskItem constructor expects an escaped input, so we need to
// make sure to re-escape it here.
DestinationFiles[i] = new TaskItem(EscapingUtilities.Escape(destinationFile));

// Copy meta-data from source to destinationFolder.
SourceFiles[i].CopyMetadataTo(DestinationFiles[i]);
}
catch (ArgumentException e)
}

if (isSuccess && SourceFolders != null && SourceFolders.Length > 0)
{
var sourceFiles = SourceFiles != null ? new List<ITaskItem>(SourceFiles) : new List<ITaskItem>();
var destinationFiles = DestinationFiles != null ? new List<ITaskItem>(DestinationFiles) : new List<ITaskItem>();

foreach (ITaskItem sourceFolder in SourceFolders)
{
Log.LogErrorWithCodeFromResources("Copy.Error", SourceFiles[i].ItemSpec, DestinationFolder.ItemSpec, e.Message);
// Clear the outputs.
DestinationFiles = Array.Empty<ITaskItem>();
return false;
}
string src = FileUtilities.NormalizePath(sourceFolder.ItemSpec);
string srcName = Path.GetFileName(src);

// Initialize the destinationFolder item.
// ItemSpec is unescaped, and the TaskItem constructor expects an escaped input, so we need to
// make sure to re-escape it here.
DestinationFiles[i] = new TaskItem(EscapingUtilities.Escape(destinationFile));
(string[] filesInFolder, _, _) = FileMatcher.Default.GetFiles(src, "**");

// Copy meta-data from source to destinationFolder.
SourceFiles[i].CopyMetadataTo(DestinationFiles[i]);
foreach (string file in filesInFolder)
{
if (!TryPathOperation(
() => Path.Combine(src, file),
sourceFolder.ItemSpec,
DestinationFolder.ItemSpec,
out string sourceFile))
{
isSuccess = false;
break;
}

if (!TryPathOperation(
() => Path.Combine(DestinationFolder.ItemSpec, srcName, file),
sourceFolder.ItemSpec,
DestinationFolder.ItemSpec,
out string destinationFile))
{
isSuccess = false;
break;
}


var item = new TaskItem(EscapingUtilities.Escape(sourceFile));
sourceFolder.CopyMetadataTo(item);
sourceFiles.Add(item);

item = new TaskItem(EscapingUtilities.Escape(destinationFile));
sourceFolder.CopyMetadataTo(item);
destinationFiles.Add(item);
}
}

SourceFiles = sourceFiles.ToArray();
DestinationFiles = destinationFiles.ToArray();
}
}
finally
{
if (!isSuccess)
{
// Clear the outputs.
DestinationFiles = Array.Empty<ITaskItem>();
}
}

return isSuccess;
}

/// <summary>
/// Tries the path operation. Logs a 'Copy.Error' if an exception is thrown.
/// </summary>
/// <param name="operation">The operation.</param>
/// <param name="src">The source to use for the log message.</param>
/// <param name="dest">The destination to use for the log message.</param>
/// <param name="resultPathOperation">The result of the path operation.</param>
/// <returns></returns>
private bool TryPathOperation(Func<string> operation, string src, string dest, out string resultPathOperation)
{
resultPathOperation = string.Empty;

try
{
resultPathOperation = operation();
}
catch (ArgumentException e)
{
Log.LogErrorWithCodeFromResources("Copy.Error", src, dest, e.Message);
return false;
}

return true;
}

Expand Down
4 changes: 4 additions & 0 deletions src/Tasks/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -2793,6 +2793,10 @@
<value>MSB3895: Retrying on ERROR_ACCESS_DENIED because environment variable MSBUILDALWAYSRETRY=1</value>
<comment>{StrBegin="MSB3895: "} LOCALIZATION: Do NOT translate MSBUILDALWAYSRETRY)</comment>
</data>
<data name="Copy.IncompatibleParameters">
<value>MSB3896: Both "{0}" and "{1}" were specified as parameters in the project file but cannot be used together. Please choose one or the other.</value>
<comment>{StrBegin="MSB3896: "}</comment>
</data>

<!--
MSB3901 - MSB3910 Task: Telemetry
Expand Down
7 changes: 6 additions & 1 deletion src/Tasks/Resources/xlf/Strings.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Tasks/Resources/xlf/Strings.de.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Tasks/Resources/xlf/Strings.es.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Tasks/Resources/xlf/Strings.fr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Tasks/Resources/xlf/Strings.it.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Tasks/Resources/xlf/Strings.ja.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Tasks/Resources/xlf/Strings.ko.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Tasks/Resources/xlf/Strings.pl.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading