Skip to content

Commit 92245f6

Browse files
Adding an item list with the list of copied files from CopyArtifacts target (#617)
* Adding an item list with the list of copied files from CopyArtifacts target * Addressing PR comments: - Simplifying the usage for customers - Updating the documentation
1 parent 1a549b8 commit 92245f6

File tree

6 files changed

+147
-2
lines changed

6 files changed

+147
-2
lines changed

src/Artifacts.UnitTests/ArtifactsTests.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,40 @@ public void InvalidDestinationFolderShouldLogAnErrorRegardingDestinationFolder()
304304
Assert.Contains($"Failed to expand the path \"{artifactPaths}", consoleLog);
305305
}
306306

307+
[Theory]
308+
[InlineData(null)]
309+
[InlineData("")]
310+
[InlineData("SomethingCustom", true)]
311+
public void ListCopiedFiles(string itemName, bool expectListCopiedFiles = false)
312+
{
313+
CreateFiles(
314+
Path.Combine("bin", "Debug"),
315+
"foo.dll",
316+
"foo.pdb");
317+
318+
DirectoryInfo artifactsPath = new DirectoryInfo(Path.Combine(TestRootPath, "artifacts"));
319+
320+
string outputPath = $"{Path.Combine("bin", "Debug")}{Path.DirectorySeparatorChar}";
321+
322+
ProjectCreator.Templates.ProjectWithArtifacts(
323+
path: Path.Combine(TestRootPath, "ProjectA.csproj"),
324+
outputPath: outputPath,
325+
artifactsPath: artifactsPath.FullName,
326+
copiedArtifactsItemName: itemName)
327+
.Target("LogCopiedFiles", afterTargets: "CopyArtifacts")
328+
.TaskMessage($"{itemName} contains %({itemName}.Identity)")
329+
.Save()
330+
.TryBuild(out bool result, out BuildOutput buildOutput)
331+
.TryGetPropertyValue("ListCopiedFilesCount", out string listCopiedFilesCount);
332+
333+
result.ShouldBeTrue(buildOutput.GetConsoleLog());
334+
335+
if (expectListCopiedFiles)
336+
{
337+
buildOutput.Messages.ShouldContain(i => i.StartsWith($"{itemName} contains"));
338+
}
339+
}
340+
307341
[Theory]
308342
[InlineData(null)]
309343
[InlineData(true)]

src/Artifacts.UnitTests/CustomProjectCreatorTemplates.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public static ProjectCreator ProjectWithArtifacts(
4444
string artifactsPath = null,
4545
string targetFramework = "net472",
4646
bool? appendTargetFrameworkToOutputPath = true,
47+
string copiedArtifactsItemName = null,
4748
Action<ProjectCreator> customAction = null,
4849
string path = null,
4950
string defaultTargets = null,
@@ -70,6 +71,7 @@ public static ProjectCreator ProjectWithArtifacts(
7071
.Property("AppendTargetFrameworkToOutputPath", appendTargetFrameworkToOutputPath.HasValue ? appendTargetFrameworkToOutputPath.ToString() : null)
7172
.Property("OutputPath", $"$(OutputPath)$(TargetFramework.ToLowerInvariant()){Path.DirectorySeparatorChar}", condition: "'$(AppendTargetFrameworkToOutputPath)' == 'true'")
7273
.Property("ArtifactsPath", artifactsPath)
74+
.Property("ArtifactsCopiedFilesItemName", copiedArtifactsItemName)
7375
.CustomAction(customAction)
7476
.Target("Build")
7577
.Target("AfterBuild", afterTargets: "Build")

src/Artifacts.UnitTests/RobocopyTests.cs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -825,6 +825,70 @@ public void CoWExceptionFallsBackToCopy(bool win32ExWithSharingViolation)
825825
customMessage: buildEngine.GetConsoleLog());
826826
}
827827

828+
[Fact]
829+
public void ListCopiedFilesIsOffByDefault()
830+
{
831+
DirectoryInfo source = CreateFiles("source", "bar.txt");
832+
833+
DirectoryInfo destination = new DirectoryInfo(Path.Combine(TestRootPath, "destination"));
834+
BuildEngine buildEngine = BuildEngine.Create();
835+
836+
Robocopy copyArtifacts = new ()
837+
{
838+
BuildEngine = buildEngine,
839+
Sources =
840+
[
841+
new MockTaskItem(source.FullName)
842+
{
843+
["DestinationFolder"] = destination.FullName,
844+
[nameof(RobocopyMetadata.IsRecursive)] = "false",
845+
},
846+
],
847+
Sleep = _ => { },
848+
};
849+
850+
copyArtifacts.Execute().ShouldBeTrue(buildEngine.GetConsoleLog());
851+
copyArtifacts.NumFilesCopied.ShouldBe(1);
852+
853+
copyArtifacts.CopiedFiles.Length.ShouldBe(0);
854+
}
855+
856+
[Fact]
857+
public void ListCopiedFilesCanBeEnabled()
858+
{
859+
DirectoryInfo source = CreateFiles(
860+
"source",
861+
"bar.txt",
862+
"foo.txt");
863+
864+
DirectoryInfo destination = new DirectoryInfo(Path.Combine(TestRootPath, "destination"));
865+
BuildEngine buildEngine = BuildEngine.Create();
866+
867+
Robocopy copyArtifacts = new ()
868+
{
869+
BuildEngine = buildEngine,
870+
Sources =
871+
[
872+
new MockTaskItem(source.FullName)
873+
{
874+
["DestinationFolder"] = destination.FullName,
875+
[nameof(RobocopyMetadata.IsRecursive)] = "false",
876+
},
877+
],
878+
ListCopiedFiles = true,
879+
Sleep = _ => { },
880+
};
881+
882+
copyArtifacts.Execute().ShouldBeTrue(buildEngine.GetConsoleLog());
883+
copyArtifacts.NumFilesCopied.ShouldBe(2);
884+
885+
copyArtifacts.CopiedFiles.Length.ShouldBe(2);
886+
copyArtifacts.CopiedFiles.All(
887+
f => new[] { "foo.txt", "bar.txt" }.Any(
888+
fileName => f.ItemSpec == Path.Combine(destination.FullName, fileName)
889+
&& f.GetMetadata("SourceFile") == Path.Combine(source.FullName, fileName))).ShouldBeTrue();
890+
}
891+
828892
private sealed class MockFileSystem : IFileSystem
829893
{
830894
private int _numCloneFileCalls;

src/Artifacts/README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ The following properties control artifacts staging:
150150
| `ArtifactsShowDiagnostics` | Enables additional logging that can be used to troubleshoot why artifacts are not being staged | `false` |
151151
| `ArtifactsShowErrorOnRetry` | Logs an error if a retry was attempted. Disable this to suppress issues while copying files | `true` |
152152
| `DisableCopyOnWrite` | Disables use of copy-on-write links (file cloning) even if the filesystem allows it. | `false` |
153+
| `ArtifactsCopiedFilesItemName` | Specifies an item name which receives the list of files that were copied. | |
153154
| `CustomBeforeArtifactsProps ` | A list of custom MSBuild projects to import **before** artifacts properties are declared. |
154155
| `CustomAfterArtifactsProps` | A list of custom MSBuild projects to import **after** Artifacts properties are declared.|
155156
| `CustomBeforeArtifactsTargets` | A list of custom MSBuild projects to import **before** Artifacts targets are declared.|
@@ -166,6 +167,20 @@ To change the default file match for artifacts, set the `DefaultArtifactsFileMat
166167

167168
<br />
168169

170+
To get the list of copied files, set the `ArtifactsCopiedFiles` property:
171+
```xml
172+
<PropertyGroup>
173+
<ArtifactsCopiedFilesItemName>MyCopiedArtifact</ArtifactsCopiedFilesItemName>
174+
</PropertyGroup>
175+
176+
<Target Name="LogAllCopiedArtifacts" AfterTargets="CopyArtifacts">
177+
<Message Text="Copied @(MyCopiedArtifact->Count()) artifact(s):" />
178+
<Message Text=" %(MyCopiedArtifact.Identity)" />
179+
</Target>
180+
```
181+
182+
<br />
183+
169184
The `<Artifact />` items specify collections of artifacts to stage. These items have the following metadata:
170185

171186
| Metadata | Description | Default |

src/Artifacts/Tasks/Robocopy.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public class Robocopy : Task
3636
private readonly ConcurrentDictionary<string, Dictionary<string, FileInfo>> _destinationDirectoryFilesCopying = new (concurrencyLevel: 1, capacity: 31, Artifacts.FileSystem.PathComparer); // Map for destination directories to track files being copied there in parallel portion of copy. Concurrent dictionaries to get TryAdd(), GetOrAdd().
3737
private readonly ActionBlock<CopyJob> _copyFileBlock;
3838
private readonly HashSet<string> _sourceFilesEncountered = new (Artifacts.FileSystem.PathComparer); // Reusable scratch space
39+
private readonly ConcurrentBag<ITaskItem> _copiedFiles = new ();
3940

4041
private TimeSpan _retryWaitInMilliseconds = TimeSpan.Zero;
4142
private int _numFilesCopied;
@@ -78,6 +79,18 @@ public Robocopy()
7879
[Required]
7980
public ITaskItem[] Sources { get; set; } = Array.Empty<ITaskItem>();
8081

82+
/// <summary>
83+
/// Gets or sets a value indicating whether to collect the list of copied files.
84+
/// When false, the CopiedFiles output parameter will be empty.
85+
/// </summary>
86+
public bool ListCopiedFiles { get; set; }
87+
88+
/// <summary>
89+
/// Gets the list of files that were successfully copied.
90+
/// </summary>
91+
[Output]
92+
public ITaskItem[] CopiedFiles => _copiedFiles.ToArray();
93+
8194
/// <summary>
8295
/// Gets or sets a value indicating whether to disable copy-on-write linking if the links are available.
8396
/// </summary>
@@ -266,6 +279,14 @@ private void CopyFileImpl(FileInfo sourceFile, FileInfo destFile, RobocopyMetada
266279
destFile.Attributes = FileAttributes.Normal;
267280
destFile.LastWriteTimeUtc = sourceFile.LastWriteTimeUtc;
268281
Log.LogMessage("{0} {1}{2} to {3}", cowLinked ? "Created copy-on-write link" : "Copied", originalSourcePath, replacementSourceFile is null ? string.Empty : $" (actually {replacementSourceFile.FullName})", destPath);
282+
283+
if (ListCopiedFiles)
284+
{
285+
TaskItem copiedFile = new (destPath);
286+
copiedFile.SetMetadata("SourceFile", originalSourcePath);
287+
_copiedFiles.Add(copiedFile);
288+
}
289+
269290
Interlocked.Increment(ref _numFilesCopied);
270291
}
271292
else

src/Artifacts/build/Microsoft.Build.Artifacts.Common.targets

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,20 @@
2727
Condition="'$(EnableArtifacts)' != 'false'"
2828
AfterTargets="$([MSBuild]::ValueOrDefault($(CopyArtifactsAfterTargets), 'AfterBuild'))"
2929
DependsOnTargets="$(CopyArtifactsDependsOn)">
30+
<PropertyGroup>
31+
<_ArtifactsListCopiedFiles Condition="'$(ArtifactsCopiedFilesItemName)' != ''">true</_ArtifactsListCopiedFiles>
32+
</PropertyGroup>
3033
<Robocopy
3134
RetryCount="$([MSBuild]::ValueOrDefault($(ArtifactsCopyRetryCount), $(CopyRetryCount)))"
3235
RetryWait="$([MSBuild]::ValueOrDefault($(ArtifactsCopyRetryDelayMilliseconds), $(CopyRetryDelayMilliseconds)))"
3336
ShowDiagnosticTrace="$(ArtifactsShowDiagnostics)"
3437
ShowErrorOnRetry="$([MSBuild]::ValueOrDefault($(ArtifactsShowErrorOnRetry), 'true'))"
3538
DisableCopyOnWrite="$([MSBuild]::ValueOrDefault($(DisableCopyOnWrite), 'false'))"
3639
Sources="@(Artifact)"
37-
Condition="@(Artifact->Count()) > 0" />
40+
ListCopiedFiles="$(_ArtifactsListCopiedFiles)"
41+
Condition="@(Artifact->Count()) > 0">
42+
<Output TaskParameter="CopiedFiles" ItemName="$(ArtifactsCopiedFilesItemName)" Condition="'$(ArtifactsCopiedFilesItemName)' != ''" />
43+
</Robocopy>
3844
</Target>
3945

4046
<Target Name="RobocopyFiles"
@@ -48,6 +54,9 @@
4854
ShowErrorOnRetry="$([MSBuild]::ValueOrDefault($(RobocopyShowErrorOnRetry), 'true'))"
4955
DisableCopyOnWrite="$([MSBuild]::ValueOrDefault($(DisableCopyOnWrite), 'false'))"
5056
Sources="@(Robocopy)"
51-
Condition="@(Robocopy->Count()) > 0"/>
57+
ListCopiedFiles="$(_ArtifactsListCopiedFiles)"
58+
Condition="@(Robocopy->Count()) > 0">
59+
<Output TaskParameter="CopiedFiles" ItemName="$(ArtifactsCopiedFilesItemName)" Condition="'$(ArtifactsCopiedFilesItemName)' != ''" />
60+
</Robocopy>
5261
</Target>
5362
</Project>

0 commit comments

Comments
 (0)