-
-
Notifications
You must be signed in to change notification settings - Fork 1k
feat(jobs): add MsBuildProperty for escaped MSBuild properties #3055
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| using System; | ||
| using System.Text; | ||
| using JetBrains.Annotations; | ||
|
|
||
| namespace BenchmarkDotNet.Jobs | ||
|
|
@@ -47,6 +48,96 @@ public MonoArgument(string value) : base(value) | |
| [PublicAPI] | ||
| public class MsBuildArgument : Argument | ||
| { | ||
| private readonly string? displayValue; | ||
|
|
||
| public MsBuildArgument(string value) : base(value) { } | ||
|
|
||
| public MsBuildArgument(string value, bool escapeSpecialCharacters) : base(escapeSpecialCharacters ? EscapeSpecialCharacters(value) : value) | ||
| { | ||
| if (escapeSpecialCharacters) | ||
| displayValue = value; | ||
| } | ||
|
|
||
| public override string ToString() => displayValue ?? base.ToString(); | ||
|
|
||
| internal static string EscapeSpecialCharacters(string value) | ||
| { | ||
| if (value is null) | ||
| throw new ArgumentNullException(nameof(value)); | ||
|
|
||
| var builder = new StringBuilder(value.Length); | ||
| foreach (var character in value) | ||
| { | ||
| switch (character) | ||
| { | ||
| // See: https://learn.microsoft.com/en-us/visualstudio/msbuild/special-characters-to-escape | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not documented thought, following chars need to be escaped also.
I've tested following tests and it failed when above chars are contained. |
||
| case '%': | ||
| builder.Append("%25"); | ||
| break; | ||
| case '$': | ||
| builder.Append("%24"); | ||
| break; | ||
| case '@': | ||
| builder.Append("%40"); | ||
| break; | ||
| case '(': | ||
| builder.Append("%28"); | ||
| break; | ||
| case ')': | ||
| builder.Append("%29"); | ||
| break; | ||
| case ';': | ||
| builder.Append("%3B"); | ||
| break; | ||
| case '?': | ||
| builder.Append("%3F"); | ||
| break; | ||
| case '*': | ||
| builder.Append("%2A"); | ||
| break; | ||
| default: | ||
| builder.Append(character); | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| return builder.ToString(); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// An MSBuild property argument (<c>/p:name=value</c>). | ||
| /// It escapes MSBuild special characters in the value (for example, semicolons) so that | ||
| /// list-like values can be passed without manual encoding. | ||
| /// </summary> | ||
| [PublicAPI] | ||
| public class MsBuildProperty : MsBuildArgument | ||
| { | ||
| public MsBuildProperty(string name, params string[] values) | ||
| : base(CreateArgumentTextRepresentation(name, values), escapeSpecialCharacters: true) | ||
| { | ||
| } | ||
|
|
||
| private static string CreateArgumentTextRepresentation(string name, string[] values) | ||
| { | ||
| if (string.IsNullOrWhiteSpace(name)) | ||
| throw new ArgumentException("Property name must be non-empty.", nameof(name)); | ||
|
|
||
| if (values is null) | ||
| throw new ArgumentNullException(nameof(values)); | ||
|
|
||
| for (int i = 0; i < values.Length; i++) | ||
| if (values[i] is null) | ||
| throw new ArgumentException("Property values can not contain null.", nameof(values)); | ||
|
|
||
| string value = values.Length switch | ||
| { | ||
| 0 => string.Empty, | ||
| 1 => values[0], | ||
| _ => string.Join(";", values) | ||
| }; | ||
|
|
||
| return $"/p:{name}={value}"; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| using BenchmarkDotNet.Jobs; | ||
| using Xunit; | ||
|
|
||
| namespace BenchmarkDotNet.Tests.Jobs; | ||
|
|
||
| public class MsBuildArgumentTests | ||
| { | ||
| [Fact] | ||
| public void EscapeSpecialCharactersIsOptIn() | ||
| { | ||
| var argument = new MsBuildArgument("/p:DefineConstants=TEST1;TEST2"); | ||
|
|
||
| Assert.Equal("/p:DefineConstants=TEST1;TEST2", argument.TextRepresentation); | ||
| Assert.Equal("/p:DefineConstants=TEST1;TEST2", argument.ToString()); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void EscapeSpecialCharactersEscapesAllMsBuildSpecialCharactersForCommandLine() | ||
| { | ||
| var argument = new MsBuildArgument("%$@();?*", escapeSpecialCharacters: true); | ||
|
|
||
| Assert.Equal("%25%24%40%28%29%3B%3F%2A", argument.TextRepresentation); | ||
| Assert.Equal("%$@();?*", argument.ToString()); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void MsBuildPropertyJoinsValuesWithSemicolonsAndEscapesThem() | ||
| { | ||
| var argument = new MsBuildProperty("DefineConstants", "TEST1", "TEST2"); | ||
|
|
||
| Assert.Equal("/p:DefineConstants=TEST1%3BTEST2", argument.TextRepresentation); | ||
| Assert.Equal("/p:DefineConstants=TEST1;TEST2", argument.ToString()); | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I though pre-defined MSBuild property(
DefineConstants) should not be set viaWithArgumentAPI.It might be better to use something like
ExtraDefineConstantsas examples.Because it's handled as global MSBuild property, and properties that are defined at project level is ignored.