Skip to content

Commit

Permalink
Add Artifact file renaming feature
Browse files Browse the repository at this point in the history
  • Loading branch information
MatthieuMEZIL committed Apr 15, 2023
1 parent cef0a8d commit 3aba48f
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 8 deletions.
76 changes: 76 additions & 0 deletions src/Artifacts.UnitTests/ArtifactsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -356,5 +356,81 @@ public void UsingSdkLogic(bool appendTargetFrameworkToOutputPath)
}.Select(i => Path.Combine(artifactsPath.FullName, i)),
ignoreOrder: true);
}

[Fact]
public void RenamedFiles()
{
CreateFiles(
Path.Combine("bin", "Debug"),
"foo.exe",
"foo.pdb",
"foo.exe.config",
"bar.dll",
"bar.pdb",
"bar.cs");

DirectoryInfo artifactsPath = new DirectoryInfo(Path.Combine(TestRootPath, "artifacts"));
DirectoryInfo artifactsPath2 = new DirectoryInfo(Path.Combine(TestRootPath, "artifacts2"));
string artifactPaths = string.Concat(artifactsPath.FullName, ";", Environment.NewLine, artifactsPath2.FullName);

ProjectCreator.Templates.SdkProjectWithArtifacts(
outputPath: Path.Combine("bin", "Debug"),
artifactsPath: artifactPaths,
defaultArtifactSource: @"bin\Debug\foo.exe;bin\Debug\bar.dll",
renamedFiles: "foo.test.exe;bar.test.dll")
.TryGetItems("Artifact", out IReadOnlyCollection<ProjectItem> artifactItems)
.TryGetPropertyValue("DefaultArtifactsSource", out string defaultArtifactsSource)
.TryBuild(out bool result, out BuildOutput buildOutput);

result.ShouldBeTrue(buildOutput.GetConsoleLog());

artifactsPath.GetFiles("*", SearchOption.AllDirectories)
.Select(i => i.FullName)
.ShouldBe(
new[]
{
"foo.test.exe",
"bar.test.dll",
}.Select(i => Path.Combine(artifactsPath.FullName, i)),
ignoreOrder: true);
artifactsPath2.GetFiles("*", SearchOption.AllDirectories)
.Select(i => i.FullName)
.ShouldBe(
new[]
{
"foo.test.exe",
"bar.test.dll",
}.Select(i => Path.Combine(artifactsPath2.FullName, i)),
ignoreOrder: true);
}

[Fact]
public void RenamedFilesWithIncorrectNumberShouldFail()
{
CreateFiles(
Path.Combine("bin", "Debug"),
"foo.exe",
"foo.pdb",
"foo.exe.config",
"bar.dll",
"bar.pdb",
"bar.cs");

DirectoryInfo artifactsPath = new DirectoryInfo(Path.Combine(TestRootPath, "artifacts"));

ProjectCreator.Templates.SdkProjectWithArtifacts(
outputPath: Path.Combine("bin", "Debug"),
artifactsPath: artifactsPath.FullName,
defaultArtifactSource: @"bin\Debug\foo.exe;bin\Debug\bar.dll",
renamedFiles: "foo.test.exe")
.TryGetItems("Artifact", out IReadOnlyCollection<ProjectItem> artifactItems)
.TryGetPropertyValue("DefaultArtifactsSource", out string defaultArtifactsSource)
.TryBuild(out bool result, out BuildOutput buildOutput);

result.ShouldBeFalse(buildOutput.GetConsoleLog());
const string expectedMessage = @"Artifact Include 'bin\Debug\foo.exe;bin\Debug\bar.dll' length does not match with RenamedFiles 'foo.test.exe'";
string errorMessage = buildOutput.ErrorEvents.Single().Message;
(errorMessage == expectedMessage || errorMessage == expectedMessage.Replace('\\', '/')).ShouldBeTrue(errorMessage);
}
}
}
6 changes: 5 additions & 1 deletion src/Artifacts.UnitTests/CustomProjectCreatorTemplates.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,9 @@ public static ProjectCreator SdkProjectWithArtifacts(
string toolsVersion = null,
string treatAsLocalProperty = null,
ProjectCollection projectCollection = null,
NewProjectFileOptions? projectFileOptions = null)
NewProjectFileOptions? projectFileOptions = null,
string defaultArtifactSource = null,
string renamedFiles = null)
{
return ProjectCreator.Create(
path,
Expand All @@ -108,6 +110,8 @@ public static ProjectCreator SdkProjectWithArtifacts(
.Property("AppendTargetFrameworkToOutputPath", appendTargetFrameworkToOutputPath.HasValue ? appendTargetFrameworkToOutputPath.ToString() : null)
.Property("OutputPath", $"$(OutputPath)$(TargetFramework.ToLowerInvariant()){Path.DirectorySeparatorChar}", condition: "'$(AppendTargetFrameworkToOutputPath)' == 'true'")
.Property("ArtifactsPath", artifactsPath)
.Property("DefaultArtifactsSource", defaultArtifactSource)
.Property("RenamedFiles", renamedFiles)
.CustomAction(customAction)
.Target("Build")
.Target("AfterBuild", afterTargets: "Build")
Expand Down
2 changes: 2 additions & 0 deletions src/Artifacts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ The following properties control artifacts staging:
| `CustomAfterArtifactsProps` | A list of custom MSBuild projects to import **after** Artifacts properties are declared.|
| `CustomBeforeArtifactsTargets` | A list of custom MSBuild projects to import **before** Artifacts targets are declared.|
| `CustomAfterArtifactsTargets` | A list of custom MSBuild projects to import **after** Artifacts targets are declared.|
| `RenamedFiles` | Specifies the list of files to rename on copy | |

**Example**

Expand All @@ -175,6 +176,7 @@ The `<Artifact />` items specify collections of artifacts to stage. These items
| `FileMatch` | A list of one or more file filters seperated by a space or semicolon to include. Wildcards include `*` and `?` | `*`|
| `FileExclude` | A list of one or more file filters seperated by a space or semicolon to exclude. Wildcards include `*` and `?` | |
| `DirExclude` | A list of one or more directory filters seperated by a space or semicolon to exclude. Wildcards include `*` and `?` | |
| `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 | |

**Example**

Expand Down
30 changes: 25 additions & 5 deletions src/Artifacts/Tasks/Robocopy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ private void CopyItems(IList<RobocopyMetadata> items)
if (hasWildcards || isRecursive)
{
string match = GetMatchString(items);
CopySearch(items, isRecursive, match, source, null);
CopySearch(items, isRecursive, match, source, subDirectory: null);
}
else
{
Expand All @@ -111,8 +111,8 @@ private void CopyItems(IList<RobocopyMetadata> items, DirectoryInfo source)
{
foreach (string destination in item.DestinationFolders)
{
FileInfo destFile = new FileInfo(Path.Combine(destination, file));
if (Verify(destFile, false, false))
FileInfo destFile = new FileInfo(Path.Combine(destination, item.RenamedFile ?? file));
if (Verify(destFile, shouldExist: false, verifyExists: false))
{
CopyFile(sourceFile, destFile, createDirs, item);
}
Expand Down Expand Up @@ -191,9 +191,29 @@ private IEnumerable<IList<RobocopyMetadata>> GetBuckets()
IList<RobocopyMetadata> allSources = new List<RobocopyMetadata>();
IList<IList<RobocopyMetadata>> allBuckets = new List<IList<RobocopyMetadata>>();

foreach (ITaskItem item in Sources ?? Enumerable.Empty<ITaskItem>())
int sourceLength = Sources?.Length ?? 0;
List<string> renamedFiles;
if (sourceLength != 0)
{
if (RobocopyMetadata.TryParse(item, Log, FileSystem.DirectoryExists, out RobocopyMetadata metadata))
renamedFiles = Sources[0].GetMetadata("RenamedFiles").Split(RobocopyMetadata.DestinationSplitter, StringSplitOptions.RemoveEmptyEntries).Select(d => d.Trim()).ToList();
if (renamedFiles.Count == 0)
{
renamedFiles = null;
}
else if (renamedFiles.Count != sourceLength)
{
Log.LogError($"Artifact Include '{string.Join(";", Sources.Select(s => s.ItemSpec))}' length does not match with RenamedFiles '{string.Join(";", renamedFiles)}'");
return allBuckets;
}
}
else
{
renamedFiles = null;
}

for (int i = 0; i < sourceLength; i++)
{
if (RobocopyMetadata.TryParse(Sources[i], Log, renamedFiles?[i], FileSystem.DirectoryExists, out RobocopyMetadata metadata))
{
allSources.Add(metadata);
}
Expand Down
7 changes: 5 additions & 2 deletions src/Artifacts/Tasks/RobocopyMetadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace Microsoft.Build.Artifacts.Tasks
{
internal sealed class RobocopyMetadata
{
private static readonly char[] DestinationSplitter = { ';' };
internal static readonly char[] DestinationSplitter = { ';' };
private static readonly char[] MultiSplits = { '\t', ' ', '\n', '\r', ';', ',' };
private static readonly char[] Wildcards = { '?', '*' };

Expand All @@ -37,6 +37,8 @@ private RobocopyMetadata()

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

public string RenamedFile { get; private set; }

public Regex[] FileRegexExcludes { get; private set; }

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

private bool OnlyNewer { get; set; }

public static bool TryParse(ITaskItem item, TaskLoggingHelper log, Func<string, bool> directoryExists, out RobocopyMetadata metadata)
public static bool TryParse(ITaskItem item, TaskLoggingHelper log, string renamedFile, Func<string, bool> directoryExists, out RobocopyMetadata metadata)
{
metadata = null;

Expand Down Expand Up @@ -97,6 +99,7 @@ public static bool TryParse(ITaskItem item, TaskLoggingHelper log, Func<string,
VerifyExists = item.GetMetadataBoolean(nameof(VerifyExists)),
AlwaysCopy = item.GetMetadataBoolean(nameof(AlwaysCopy), defaultValue: false),
OnlyNewer = item.GetMetadataBoolean(nameof(OnlyNewer), defaultValue: false),
RenamedFile = renamedFile,
};

foreach (string destination in item.GetMetadata("DestinationFolder").Split(DestinationSplitter, StringSplitOptions.RemoveEmptyEntries).Select(d => d.Trim()))
Expand Down
1 change: 1 addition & 0 deletions src/Artifacts/build/Microsoft.Build.Artifacts.targets
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
* $(TargetFramworks) is specified in which case the artifacts are copied in the outer build
-->
<Artifact Include="$(DefaultArtifactsSource)"
RenamedFiles="$(RenamedFiles)"
DestinationFolder="$(ArtifactsPath)"
FileMatch="$([MSBuild]::ValueOrDefault($(DefaultArtifactsFileMatch), '*exe *dll *exe.config *nupkg'))"
FileExclude="$(DefaultArtifactsFileExclude)"
Expand Down

0 comments on commit 3aba48f

Please sign in to comment.