Skip to content

Commit bc0e8ae

Browse files
authored
Fixes for MSBuild task: (#444)
- Parameter validation no longer fails on valid params - Fixed props fle path to LottieGen dll - Fixed path generation for LottieGen exe - Converted parameter types which are not supported by MSBuild to acceptable types - Added escaping to command-line args to avoid mangling file paths
1 parent fdfcd08 commit bc0e8ae

File tree

2 files changed

+81
-15
lines changed

2 files changed

+81
-15
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
22
<UsingTask
33
TaskName="LottieGen.Task.LottieGen"
4-
AssemblyFile="$(MsBuildThisFileDirectory)..\bin\LottieGen.MsBuild.dll"/>
4+
AssemblyFile="$(MsBuildThisFileDirectory)..\..\bin\LottieGen.MsBuild.dll"/>
55
</Project>
66

LottieGen/MSBuildTask/LottieGen.cs

Lines changed: 80 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Collections.Generic;
77
using System.IO;
88
using System.Reflection;
9+
using System.Text;
910
using Microsoft.Build.Framework;
1011
using Microsoft.Build.Utilities;
1112

@@ -31,11 +32,8 @@ public sealed class LottieGen : Microsoft.Build.Utilities.ToolTask
3132

3233
protected override string ToolName => "LottieGen.exe";
3334

34-
/// <summary>
35-
/// Optional path to LottieGen.exe. If not specified, LottieGen.exe
36-
/// is expected to be in the same directory as the task's DLL.
37-
/// </summary>
38-
public string? LottieGenExePath { get; set; }
35+
// WARNING: Nullable value types are not supported by MSBuild and will result in an error
36+
// when passed into the task through an MSBuild project file.
3937

4038
/// <summary>
4139
/// The Lottie file to process.
@@ -82,12 +80,12 @@ public sealed class LottieGen : Microsoft.Build.Utilities.ToolTask
8280

8381
/// <summary>
8482
/// The lowest UAP version on which the result must run.Defaults
85-
/// to 7. Must be 7 or higher.Code will be generated that will
83+
/// to 7. Must be 7 or higher. Code will be generated that will
8684
/// run down to this version.If less than TargetUapVersion,
8785
/// extra code will be generated if necessary to support the
8886
/// lower versions.
8987
/// </summary>
90-
public uint? MinimumUapVersion { get; set; }
88+
public uint MinimumUapVersion { get; set; }
9189

9290
/// <summary>
9391
/// Specifies the namespace for the generated code. Defaults to
@@ -134,7 +132,7 @@ public sealed class LottieGen : Microsoft.Build.Utilities.ToolTask
134132
/// minimum SDK version required to compile the generated code.
135133
/// If not specified, defaults to the latest UAP version.
136134
/// </summary>
137-
public uint? TargetUapVersion { get; set; }
135+
public uint TargetUapVersion { get; set; }
138136

139137
/// <summary>
140138
/// Prevents any information from being included that could change
@@ -147,7 +145,7 @@ public sealed class LottieGen : Microsoft.Build.Utilities.ToolTask
147145
/// <summary>
148146
/// Generates code for a particular WinUI version. Defaults to 2.4.
149147
/// </summary>
150-
public Version? WinUIVersion { get; set; }
148+
public string? WinUIVersion { get; set; }
151149

152150
public override bool Execute()
153151
{
@@ -223,13 +221,13 @@ protected override string GenerateCommandLineCommands()
223221
AddOptionalBool(nameof(DisableTranslationOptimizer), DisableTranslationOptimizer);
224222
AddOptionalBool(nameof(GenerateColorBindings), GenerateColorBindings);
225223
AddOptionalBool(nameof(GenerateDependencyObject), GenerateDependencyObject);
226-
AddOptional(nameof(MinimumUapVersion), MinimumUapVersion);
224+
AddOptional(nameof(MinimumUapVersion), MinimumUapVersion > 0 ? MinimumUapVersion : null);
227225
AddOptional(nameof(Namespace), Namespace);
228226
AddArg(nameof(OutputFolder), OutputFolder!);
229227
AddOptionalBool(nameof(Public), Public);
230228
AddOptional(nameof(RootNamespace), RootNamespace);
231229
AddOptionalBool(nameof(StrictMode), StrictMode);
232-
AddOptional(nameof(TargetUapVersion), TargetUapVersion);
230+
AddOptional(nameof(TargetUapVersion), TargetUapVersion > 0 ? TargetUapVersion : null);
233231
AddOptional(nameof(WinUIVersion), WinUIVersion);
234232

235233
var result = string.Join(" ", args);
@@ -253,15 +251,73 @@ void AddOptionalBool(string parameterName, bool value)
253251
}
254252

255253
void AddArg(string parameterName, string value)
256-
=> args.Add($"-{parameterName} {value}");
254+
{
255+
args.Add($"-{parameterName}");
256+
args.Add(EscapeCmdLineArg(value));
257+
}
258+
}
259+
260+
/// <summary>
261+
/// Escapes a command-line argument to ensure that it can be passed through to another application without mangling.
262+
/// </summary>
263+
/// <remarks>
264+
/// The string is wrapped in quotes to ensure that any spaces are considered part of the value. Backslashes and quotes
265+
/// are escaped to ensure they are not interpreted as metacharacters.
266+
/// </remarks>
267+
/// <param name="arg">The argument to escape.</param>
268+
/// <returns>The escaped argument.</returns>
269+
string EscapeCmdLineArg(string arg)
270+
{
271+
// From MSDN "Everyone quotes command line arguments the wrong way"
272+
StringBuilder result = new StringBuilder();
273+
274+
// Wrap the arg in quotes
275+
result.Append('\"');
276+
277+
for (int i = 0; ; ++i)
278+
{
279+
int numBackslashes = 0;
280+
while (i < arg.Length && arg[i] == '\\')
281+
{
282+
++i;
283+
++numBackslashes;
284+
}
285+
286+
if (i >= arg.Length)
287+
{
288+
// End of string.
289+
// Doubling any trailing backslashes gives us an even number followed by our final double-quote.
290+
// Cmdline will replace each pair with a single backslash (back to where we started), but treat
291+
// the quote as a metacharacter and remove it.
292+
result.Append('\\', numBackslashes * 2);
293+
break;
294+
}
295+
else if (arg[i] == '"')
296+
{
297+
// Found a double-quote in the string.
298+
// Doubling any preceding backslashes and adding one gives us an odd number followed by the double-quote.
299+
// Cmdline will replace each pair with a single backslash and convert the ending \" to a literal quote.
300+
result.Append('\\', (numBackslashes * 2) + 1);
301+
result.Append(arg[i]);
302+
}
303+
else
304+
{
305+
// In all other cases, just pass through the characters as-is.
306+
result.Append('\\', numBackslashes);
307+
result.Append(arg[i]);
308+
}
309+
}
310+
311+
result.Append('\"');
312+
return result.ToString();
257313
}
258314

259315
// Provides the default path to the tool. Ignored if
260316
// the <ToolPath/> property is set.
261317
// By default we expect the tool to be in the same directory
262318
// as the assembly that this class is in.
263319
protected override string GenerateFullPathToTool()
264-
=> Path.Combine(Assembly.GetExecutingAssembly().Location, ToolName);
320+
=> Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), ToolName);
265321

266322
protected override bool ValidateParameters()
267323
{
@@ -293,7 +349,17 @@ protected override bool ValidateParameters()
293349
break;
294350
}
295351

296-
return hasErrors;
352+
if (WinUIVersion is not null)
353+
{
354+
Version version;
355+
if (!Version.TryParse(WinUIVersion, out version))
356+
{
357+
Log.LogError($"Invalid version string \"{WinUIVersion!}\".");
358+
hasErrors = true;
359+
}
360+
}
361+
362+
return !hasErrors;
297363
}
298364

299365
protected override void LogEventsFromTextOutput(string singleLine, MessageImportance messageImportance)

0 commit comments

Comments
 (0)