Skip to content

Commit 3aba48f

Browse files
committed
Add Artifact file renaming feature
1 parent cef0a8d commit 3aba48f

6 files changed

Lines changed: 114 additions & 8 deletions

File tree

src/Artifacts.UnitTests/ArtifactsTests.cs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,5 +356,81 @@ public void UsingSdkLogic(bool appendTargetFrameworkToOutputPath)
356356
}.Select(i => Path.Combine(artifactsPath.FullName, i)),
357357
ignoreOrder: true);
358358
}
359+
360+
[Fact]
361+
public void RenamedFiles()
362+
{
363+
CreateFiles(
364+
Path.Combine("bin", "Debug"),
365+
"foo.exe",
366+
"foo.pdb",
367+
"foo.exe.config",
368+
"bar.dll",
369+
"bar.pdb",
370+
"bar.cs");
371+
372+
DirectoryInfo artifactsPath = new DirectoryInfo(Path.Combine(TestRootPath, "artifacts"));
373+
DirectoryInfo artifactsPath2 = new DirectoryInfo(Path.Combine(TestRootPath, "artifacts2"));
374+
string artifactPaths = string.Concat(artifactsPath.FullName, ";", Environment.NewLine, artifactsPath2.FullName);
375+
376+
ProjectCreator.Templates.SdkProjectWithArtifacts(
377+
outputPath: Path.Combine("bin", "Debug"),
378+
artifactsPath: artifactPaths,
379+
defaultArtifactSource: @"bin\Debug\foo.exe;bin\Debug\bar.dll",
380+
renamedFiles: "foo.test.exe;bar.test.dll")
381+
.TryGetItems("Artifact", out IReadOnlyCollection<ProjectItem> artifactItems)
382+
.TryGetPropertyValue("DefaultArtifactsSource", out string defaultArtifactsSource)
383+
.TryBuild(out bool result, out BuildOutput buildOutput);
384+
385+
result.ShouldBeTrue(buildOutput.GetConsoleLog());
386+
387+
artifactsPath.GetFiles("*", SearchOption.AllDirectories)
388+
.Select(i => i.FullName)
389+
.ShouldBe(
390+
new[]
391+
{
392+
"foo.test.exe",
393+
"bar.test.dll",
394+
}.Select(i => Path.Combine(artifactsPath.FullName, i)),
395+
ignoreOrder: true);
396+
artifactsPath2.GetFiles("*", SearchOption.AllDirectories)
397+
.Select(i => i.FullName)
398+
.ShouldBe(
399+
new[]
400+
{
401+
"foo.test.exe",
402+
"bar.test.dll",
403+
}.Select(i => Path.Combine(artifactsPath2.FullName, i)),
404+
ignoreOrder: true);
405+
}
406+
407+
[Fact]
408+
public void RenamedFilesWithIncorrectNumberShouldFail()
409+
{
410+
CreateFiles(
411+
Path.Combine("bin", "Debug"),
412+
"foo.exe",
413+
"foo.pdb",
414+
"foo.exe.config",
415+
"bar.dll",
416+
"bar.pdb",
417+
"bar.cs");
418+
419+
DirectoryInfo artifactsPath = new DirectoryInfo(Path.Combine(TestRootPath, "artifacts"));
420+
421+
ProjectCreator.Templates.SdkProjectWithArtifacts(
422+
outputPath: Path.Combine("bin", "Debug"),
423+
artifactsPath: artifactsPath.FullName,
424+
defaultArtifactSource: @"bin\Debug\foo.exe;bin\Debug\bar.dll",
425+
renamedFiles: "foo.test.exe")
426+
.TryGetItems("Artifact", out IReadOnlyCollection<ProjectItem> artifactItems)
427+
.TryGetPropertyValue("DefaultArtifactsSource", out string defaultArtifactsSource)
428+
.TryBuild(out bool result, out BuildOutput buildOutput);
429+
430+
result.ShouldBeFalse(buildOutput.GetConsoleLog());
431+
const string expectedMessage = @"Artifact Include 'bin\Debug\foo.exe;bin\Debug\bar.dll' length does not match with RenamedFiles 'foo.test.exe'";
432+
string errorMessage = buildOutput.ErrorEvents.Single().Message;
433+
(errorMessage == expectedMessage || errorMessage == expectedMessage.Replace('\\', '/')).ShouldBeTrue(errorMessage);
434+
}
359435
}
360436
}

src/Artifacts.UnitTests/CustomProjectCreatorTemplates.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,9 @@ public static ProjectCreator SdkProjectWithArtifacts(
9090
string toolsVersion = null,
9191
string treatAsLocalProperty = null,
9292
ProjectCollection projectCollection = null,
93-
NewProjectFileOptions? projectFileOptions = null)
93+
NewProjectFileOptions? projectFileOptions = null,
94+
string defaultArtifactSource = null,
95+
string renamedFiles = null)
9496
{
9597
return ProjectCreator.Create(
9698
path,
@@ -108,6 +110,8 @@ public static ProjectCreator SdkProjectWithArtifacts(
108110
.Property("AppendTargetFrameworkToOutputPath", appendTargetFrameworkToOutputPath.HasValue ? appendTargetFrameworkToOutputPath.ToString() : null)
109111
.Property("OutputPath", $"$(OutputPath)$(TargetFramework.ToLowerInvariant()){Path.DirectorySeparatorChar}", condition: "'$(AppendTargetFrameworkToOutputPath)' == 'true'")
110112
.Property("ArtifactsPath", artifactsPath)
113+
.Property("DefaultArtifactsSource", defaultArtifactSource)
114+
.Property("RenamedFiles", renamedFiles)
111115
.CustomAction(customAction)
112116
.Target("Build")
113117
.Target("AfterBuild", afterTargets: "Build")

src/Artifacts/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ The following properties control artifacts staging:
151151
| `CustomAfterArtifactsProps` | A list of custom MSBuild projects to import **after** Artifacts properties are declared.|
152152
| `CustomBeforeArtifactsTargets` | A list of custom MSBuild projects to import **before** Artifacts targets are declared.|
153153
| `CustomAfterArtifactsTargets` | A list of custom MSBuild projects to import **after** Artifacts targets are declared.|
154+
| `RenamedFiles` | Specifies the list of files to rename on copy | |
154155

155156
**Example**
156157

@@ -175,6 +176,7 @@ The `<Artifact />` items specify collections of artifacts to stage. These items
175176
| `FileMatch` | A list of one or more file filters seperated by a space or semicolon to include. Wildcards include `*` and `?` | `*`|
176177
| `FileExclude` | A list of one or more file filters seperated by a space or semicolon to exclude. Wildcards include `*` and `?` | |
177178
| `DirExclude` | A list of one or more directory filters seperated by a space or semicolon to exclude. Wildcards include `*` and `?` | |
179+
| `RenamedFiles` | A list of files separated by a semicolon matching Include length to rename source files on copy. RenamedFiles should not contain directory as it is provided through DestinationFolder | |
178180

179181
**Example**
180182

src/Artifacts/Tasks/Robocopy.cs

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ private void CopyItems(IList<RobocopyMetadata> items)
9090
if (hasWildcards || isRecursive)
9191
{
9292
string match = GetMatchString(items);
93-
CopySearch(items, isRecursive, match, source, null);
93+
CopySearch(items, isRecursive, match, source, subDirectory: null);
9494
}
9595
else
9696
{
@@ -111,8 +111,8 @@ private void CopyItems(IList<RobocopyMetadata> items, DirectoryInfo source)
111111
{
112112
foreach (string destination in item.DestinationFolders)
113113
{
114-
FileInfo destFile = new FileInfo(Path.Combine(destination, file));
115-
if (Verify(destFile, false, false))
114+
FileInfo destFile = new FileInfo(Path.Combine(destination, item.RenamedFile ?? file));
115+
if (Verify(destFile, shouldExist: false, verifyExists: false))
116116
{
117117
CopyFile(sourceFile, destFile, createDirs, item);
118118
}
@@ -191,9 +191,29 @@ private IEnumerable<IList<RobocopyMetadata>> GetBuckets()
191191
IList<RobocopyMetadata> allSources = new List<RobocopyMetadata>();
192192
IList<IList<RobocopyMetadata>> allBuckets = new List<IList<RobocopyMetadata>>();
193193

194-
foreach (ITaskItem item in Sources ?? Enumerable.Empty<ITaskItem>())
194+
int sourceLength = Sources?.Length ?? 0;
195+
List<string> renamedFiles;
196+
if (sourceLength != 0)
195197
{
196-
if (RobocopyMetadata.TryParse(item, Log, FileSystem.DirectoryExists, out RobocopyMetadata metadata))
198+
renamedFiles = Sources[0].GetMetadata("RenamedFiles").Split(RobocopyMetadata.DestinationSplitter, StringSplitOptions.RemoveEmptyEntries).Select(d => d.Trim()).ToList();
199+
if (renamedFiles.Count == 0)
200+
{
201+
renamedFiles = null;
202+
}
203+
else if (renamedFiles.Count != sourceLength)
204+
{
205+
Log.LogError($"Artifact Include '{string.Join(";", Sources.Select(s => s.ItemSpec))}' length does not match with RenamedFiles '{string.Join(";", renamedFiles)}'");
206+
return allBuckets;
207+
}
208+
}
209+
else
210+
{
211+
renamedFiles = null;
212+
}
213+
214+
for (int i = 0; i < sourceLength; i++)
215+
{
216+
if (RobocopyMetadata.TryParse(Sources[i], Log, renamedFiles?[i], FileSystem.DirectoryExists, out RobocopyMetadata metadata))
197217
{
198218
allSources.Add(metadata);
199219
}

src/Artifacts/Tasks/RobocopyMetadata.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ namespace Microsoft.Build.Artifacts.Tasks
1515
{
1616
internal sealed class RobocopyMetadata
1717
{
18-
private static readonly char[] DestinationSplitter = { ';' };
18+
internal static readonly char[] DestinationSplitter = { ';' };
1919
private static readonly char[] MultiSplits = { '\t', ' ', '\n', '\r', ';', ',' };
2020
private static readonly char[] Wildcards = { '?', '*' };
2121

@@ -37,6 +37,8 @@ private RobocopyMetadata()
3737

3838
public string[] FileMatches { get; private set; }
3939

40+
public string RenamedFile { get; private set; }
41+
4042
public Regex[] FileRegexExcludes { get; private set; }
4143

4244
public Regex[] FileRegexMatches { get; private set; }
@@ -61,7 +63,7 @@ private RobocopyMetadata()
6163

6264
private bool OnlyNewer { get; set; }
6365

64-
public static bool TryParse(ITaskItem item, TaskLoggingHelper log, Func<string, bool> directoryExists, out RobocopyMetadata metadata)
66+
public static bool TryParse(ITaskItem item, TaskLoggingHelper log, string renamedFile, Func<string, bool> directoryExists, out RobocopyMetadata metadata)
6567
{
6668
metadata = null;
6769

@@ -97,6 +99,7 @@ public static bool TryParse(ITaskItem item, TaskLoggingHelper log, Func<string,
9799
VerifyExists = item.GetMetadataBoolean(nameof(VerifyExists)),
98100
AlwaysCopy = item.GetMetadataBoolean(nameof(AlwaysCopy), defaultValue: false),
99101
OnlyNewer = item.GetMetadataBoolean(nameof(OnlyNewer), defaultValue: false),
102+
RenamedFile = renamedFile,
100103
};
101104

102105
foreach (string destination in item.GetMetadata("DestinationFolder").Split(DestinationSplitter, StringSplitOptions.RemoveEmptyEntries).Select(d => d.Trim()))

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
* $(TargetFramworks) is specified in which case the artifacts are copied in the outer build
3535
-->
3636
<Artifact Include="$(DefaultArtifactsSource)"
37+
RenamedFiles="$(RenamedFiles)"
3738
DestinationFolder="$(ArtifactsPath)"
3839
FileMatch="$([MSBuild]::ValueOrDefault($(DefaultArtifactsFileMatch), '*exe *dll *exe.config *nupkg'))"
3940
FileExclude="$(DefaultArtifactsFileExclude)"

0 commit comments

Comments
 (0)