Skip to content

Commit

Permalink
Update PMI to use the new System.CommandLine parser
Browse files Browse the repository at this point in the history
Replace PMI's home-grown command line parsing with the new System.CommandLine
version.

Syntax changes slightly as the "suffix" options (eg `PREPALL-QUIET`) now need
to be separated out (`PREPALL --quiet`). Only real "breaking" change is that the
method index for `PREPALL` and `PREPONE` is now a named option.

With this new package we get built in help and (in some shells) command line
completion.

I plan to eventually migrate all the utilites over to this package as the one
they are using is now orphaned and unsupported. See dotnet#193.
  • Loading branch information
AndyAyersMS committed May 24, 2019
1 parent f1191cf commit 6164fb7
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 96 deletions.
9 changes: 5 additions & 4 deletions src/pmi/PMIDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -166,16 +166,17 @@ private static PrepAllResult Compute(PrepAllInfo pi)
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;

// Fetch our command line. Split off the arguments.
// Fetch our command line, and fix up the arguments.

string newArgs = $"PREPALL --method {pi.methodToPrep}";
#if NETCOREAPP2_1 || NETCOREAPP2_2 || NETCOREAPP3_0
// For .Net Core the PMI assembly is an argument to dotnet.
string newCommandLine = Environment.CommandLine.Replace("DRIVEALL", "PREPALL");
string newCommandLine = Environment.CommandLine.Replace("DRIVEALL", newArgs);
#else
// For .Net Framework the OS knows how to bootstrap .Net when
// passed the PMI assembly as an executable.
string newCommandLine = "PREPALL \"" + pi.assemblyName + "\"";
string newCommandLine = newArgs + " \"" + pi.assemblyName + "\"";
#endif
newCommandLine += " " + pi.methodToPrep;

Process thisProcess = Process.GetCurrentProcess();
string driverName = thisProcess.MainModule.FileName;
Expand Down
226 changes: 134 additions & 92 deletions src/pmi/pmi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

using System;
using System.Collections.Generic;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.IO;
using System.Linq;
using System.Numerics;
Expand Down Expand Up @@ -625,7 +627,7 @@ public override void AttemptMethod(Type type, MethodBase method)
{
Console.WriteLine($"PREPONE type# {typeCount} method# {methodCount} {type.FullName}::{method.Name}");
TimeSpan elapsedFunc = PrepareMethod(type, method);
Console.WriteLine($"Completed method {type.FullName}::{method.Name}");
Console.Write($"Completed method {type.FullName}::{method.Name}");
if (elapsedFunc != TimeSpan.MinValue)
{
Console.WriteLine($", elapsed ms: {elapsedFunc.TotalMilliseconds:F2}");
Expand Down Expand Up @@ -973,10 +975,11 @@ bool Work(Type type)
{
visitor.StartMethod(type, methodBase);
keepGoing = visitor.FinishMethod(type, methodBase);
if (!keepGoing)
{
break;
}
}

if (!keepGoing)
{
break;
}
}
}
Expand Down Expand Up @@ -1315,121 +1318,160 @@ private static void EnsureTimerCallbackIsJitted()
// >= 100 - failure
public static int Main(string[] args)
{
if (args.Length < 2)
{
return Usage();
}

// Tracing infrastructure unconditionally creates a Timer and uses it for checking
// whether tracing has been enabled. Since the Timer callback is called on a worker thread,
// we may get corrupted disasm output. To prevent that, force jitting of Timer callback infrastructure
// before we PMI any method.
EnsureTimerCallbackIsJitted();

string command = args[0].ToUpper();
string assemblyName = args[1];
int methodToPrep = -1; // For PREPONE, PREPALL. For PREPALL, this is the first method to prep.
// --- COUNT ---
Command countCommand = new Command("COUNT");
{
countCommand.Description = "Count the number of types and methods in an assembly.";

Visitor v = null;
Argument<string> assemblyArgument = new Argument<string>();
assemblyArgument.Arity = ArgumentArity.ExactlyOne;
assemblyArgument.Name = "assemblyName";
assemblyArgument.Description = "Assembly file to process";
countCommand.AddArgument(assemblyArgument);

int dashIndex = command.IndexOf('-');
string rootCommand = dashIndex < 0 ? command : command.Substring(0, dashIndex);
switch (rootCommand)
{
case "DRIVEALL":
case "COUNT":
if (args.Length < 2)
{
Console.WriteLine("ERROR: too few arguments");
return Usage();
}
else if (args.Length > 2)
countCommand.Handler = CommandHandler.Create<string>((assemblyName) =>
{
if (File.Exists(assemblyName))
{
Console.WriteLine("ERROR: too many arguments");
return Usage();
}
if (rootCommand == "DRIVEALL")
{
return PMIDriver.PMIDriver.Drive(assemblyName);
Visitor v = new Counter();
Worker w = new Worker(v, false);
return w.Work(assemblyName);
}
v = new Counter();
break;
Console.WriteLine($"Failed: assembly '{assemblyName}' does not exist");
return 101;
});
}

case "PREPALL":
case "PREPONE":
if (args.Length < 3)
{
methodToPrep = 0;
}
else if (args.Length > 3)
{
Console.WriteLine("ERROR: too many arguments");
return Usage();
}
else
{
try
{
methodToPrep = Convert.ToInt32(args[2]);
}
catch (System.FormatException)
{
Console.WriteLine("ERROR: illegal method number");
return Usage();
}
}
// --- PREPALL ---
Command prepallCommand = new Command("PREPALL");
{
prepallCommand.Description = "JIT all the methods in an assembly or all assemblies in a directory tree.";

bool all = command.IndexOf("ALL") > 0;
bool verbose = !(command.IndexOf("QUIET") > 0);
bool time = verbose || command.IndexOf("TIME") > 0;
Argument<string> assemblyArgument = new Argument<string>();
assemblyArgument.Arity = ArgumentArity.ExactlyOne;
assemblyArgument.Name = "assemblyName";
assemblyArgument.Description = "Assembly file to process, or directory to recursively traverse";
prepallCommand.AddArgument(assemblyArgument);

if (all)
Option methodOption = new Option("--method", "Index of the first method to jit", new Argument<int>(0) { Arity = ArgumentArity.ExactlyOne });
Option cctorOption = new Option("--cctors", "Try and invoke cctors first", new Argument<bool>());
Option quietOption = new Option("--quiet", "Don't show progress messages", new Argument<bool>());
Option timeOption = new Option("--time", "Show elapsed time information", new Argument<bool>());
prepallCommand.AddOption(methodOption);
prepallCommand.AddOption(cctorOption);
prepallCommand.AddOption(quietOption);
prepallCommand.AddOption(timeOption);

prepallCommand.Handler = CommandHandler.Create<string, int, bool, bool, bool>((assemblyName, method, cctors, quiet, time) =>
{
if (File.Exists(assemblyName))
{
v = new PrepareAll(methodToPrep, verbose, time);
Visitor v = new PrepareAll(method, !quiet, time);
Worker w = new Worker(v, cctors);
return w.Work(assemblyName);
}
else
#if NETCOREAPP2_1 || NETCOREAPP2_2 || NETCOREAPP3_0
else if (Directory.Exists(assemblyName))
{
v = new PrepareOne(methodToPrep, verbose, time);
EnumerationOptions options = new EnumerationOptions();
options.RecurseSubdirectories = true;
IEnumerable<string> exeFiles = Directory.EnumerateFiles(assemblyName, "*.exe", options);
IEnumerable<string> dllFiles = Directory.EnumerateFiles(assemblyName, "*.dll", options);
IEnumerable<string> allFiles = exeFiles.Concat(dllFiles);
Visitor v = new PrepareAll(method, !quiet, time);
Worker w = new Worker(v, cctors);
return w.Work(allFiles);
}
break;
#endif
default:
Console.WriteLine("ERROR: Unknown command {0}", command);
return Usage();
Console.WriteLine($"Failed: '{assemblyName}' does not exist");
return 101;
});
}

bool runCctors = command.IndexOf("CCTORS") > 0;
// --- PREPONE ---
Command preponeCommand = new Command("PREPONE");
{
preponeCommand.Description = "JIT exactly one method in an assembly.";

Worker w = new Worker(v, runCctors);
int result = 0;
string msg = "a file";
Argument<string> assemblyArgument = new Argument<string>();
assemblyArgument.Arity = ArgumentArity.ExactlyOne;
assemblyArgument.Name = "assemblyName";
assemblyArgument.Description = "Assembly file to process";
preponeCommand.AddArgument(assemblyArgument);

#if NETCOREAPP2_1 || NETCOREAPP2_2 || NETCOREAPP3_0
msg += " or a directory";
if (Directory.Exists(assemblyName))
{
EnumerationOptions options = new EnumerationOptions();
options.RecurseSubdirectories = true;
IEnumerable<string> exeFiles = Directory.EnumerateFiles(assemblyName, "*.exe", options);
IEnumerable<string> dllFiles = Directory.EnumerateFiles(assemblyName, "*.dll", options);
IEnumerable<string> allFiles = exeFiles.Concat(dllFiles);
result = w.Work(allFiles);
}
else
#endif
Option methodOption = new Option("--method", "Index of the method to jit", new Argument<int>(0) { Arity = ArgumentArity.ExactlyOne });
Option cctorOption = new Option("--cctors", "Try and invoke cctors first", new Argument<bool>());
Option quietOption = new Option("--quiet", "Don't show progress messages", new Argument<bool>());
Option timeOption = new Option("--time", "Show elapsed time information", new Argument<bool>());
preponeCommand.AddOption(methodOption);
preponeCommand.AddOption(cctorOption);
preponeCommand.AddOption(quietOption);
preponeCommand.AddOption(timeOption);

if (File.Exists(assemblyName))
{
result = w.Work(assemblyName);
preponeCommand.Handler = CommandHandler.Create<string, int, bool, bool, bool>((assemblyName, method, cctors, quiet, time) =>
{
if (File.Exists(assemblyName))
{
Visitor v = new PrepareOne(method, !quiet, time);
Worker w = new Worker(v, cctors);
return w.Work(assemblyName);
}
Console.WriteLine($"Failed: '{assemblyName}' does not exist");
return 101;
});
}
else

// --- DRIVEALL ---
Command driveallCommand = new Command("DRIVEALL");
{
Console.WriteLine($"ERROR: {assemblyName} is not {msg}");
result = 101;
driveallCommand.Description = "JIT all the methods in an assembly robustly, skipping methods with asserts.";

Argument<string> assemblyArgument = new Argument<string>();
assemblyArgument.Arity = ArgumentArity.ExactlyOne;
assemblyArgument.Name = "assemblyName";
assemblyArgument.Description = "Assembly file to process";
driveallCommand.AddArgument(assemblyArgument);

Option cctorOption = new Option("--cctors", "Try and invoke cctors first", new Argument<bool>());
Option quietOption = new Option("--quiet", "Don't show progress messages", new Argument<bool>());
Option timeOption = new Option("--time", "Show elapsed time information", new Argument<bool>());
driveallCommand.AddOption(cctorOption);
driveallCommand.AddOption(quietOption);
driveallCommand.AddOption(timeOption);

// Options are ignored here, as DRIVEALL will grab the command line for child invocations of PREPALL.
// By including them here we validate them early.
driveallCommand.Handler = CommandHandler.Create<string, bool, bool, bool>((assemblyName, cctors, quiet, time) =>
{
if (File.Exists(assemblyName))
{
return PMIDriver.PMIDriver.Drive(assemblyName);
}
Console.WriteLine($"Failed: '{assemblyName}' does not exist");
return 101;
});
}

return result;
// --- ROOT ---
RootCommand rootCommand = new RootCommand();
rootCommand.AddCommand(countCommand);
rootCommand.AddCommand(prepallCommand);
rootCommand.AddCommand(preponeCommand);
rootCommand.AddCommand(driveallCommand);

// Parse and execute
return rootCommand.InvokeAsync(args).Result;
}
}
1 change: 1 addition & 0 deletions src/pmi/pmi.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<Import Project="$([MSBuild]::GetPathOfFileAbove(target-framework.props))" />

<ItemGroup>
<PackageReference Include="System.CommandLine.Experimental" Version="0.2.0-alpha.19272.1" />
<PackageReference Include="System.Management" Version="4.5.0" />
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
</ItemGroup>
Expand Down

0 comments on commit 6164fb7

Please sign in to comment.