Skip to content

Replace McMaster with CliFx in ilspycmd - #4306

Draft
christophwille wants to merge 3 commits into
masterfrom
christophwille/ilspycmd-clifx
Draft

christophwille wants to merge 3 commits into
masterfrom
christophwille/ilspycmd-clifx

Conversation

@christophwille

Copy link
Copy Markdown
Member

Draft: one of three candidate ports of ilspycmd off McMaster.Extensions.CommandLineUtils (System.CommandLine in #4305, ConsoleAppFramework, CliFx), to be compared side by side. This one uses CliFx 3.0.1 (source-generated since 3.0; no runtime dependencies on net10.0).

The contract tests from #4303 are the oracle: ICSharpCode.ILSpyCmd.Tests is green (111 tests, the 108 existing ones plus three rows added here that pin spellings the evaluated libraries would otherwise change: a bare -usepdb followed by a file name, a multi-value option followed by a file name, and -pd being an error rather than -p -d). Error and help wording changed; option and argument behavior did not.

Shape (the same in all three drafts)

  • ILSpyCmdProgram is now the engine with plain settable properties; it validates the option combination itself (ValidateUsage: input files, -r directories, -usepdb/settings files exist, -p/-d need -o) and runs the request. It no longer knows a parser. ValidationAttributes.cs and the unused IHostEnvironment are gone, so Microsoft.Extensions.Hosting leaves the project too.
  • LegacyCommand is the file that knows the library. It defines the verb-less command line and binds it into an ILSpyCmdProgram. Retiring the verb-less form later means deleting it (here: together with LegacyArgumentRewriter).

How CliFx fits

  • LegacyCommand is CliFx's default command ([Command] without a name, a partial class with [CommandOption]/[CommandParameter] properties and a generated Descriptor). Verbs become named commands ([Command("dump table")], multi-word names nest by prefix) registered next to it; routing takes the longest leading token sequence that names a command, otherwise the default command. A file literally named like a verb needs ./verb.
  • CliFx cannot parse the legacy grammar directly. Its parser reads positional parameters only until the first option, lets an option collect every following bare token (-r dir sample.dll would make the assembly a reference path), splits every single-dash token into one-letter options (-il is -i -l, short names are one char), and has no :/= delimiters. LegacyArgumentRewriter therefore turns the raw arguments into [input files..., --name value ...] first, using the generated descriptor for arity and short names plus a five-entry table for the multi-letter aliases (-il -lv -ds -genpdb -usepdb). It also rejects what CliFx would misread (unknown options, -pd, --) with the old exit code.
  • CliFx reports every usage mistake it detects itself with exit code 1 (not configurable) and prints the help text after it; ilspycmd never used 1, so the host maps 1 to EX_USAGE. The command's own exit code is read back from the instance, which is handed to CliFx through UseTypeInstantiator.
  • CliFx's stock enum conversion lets a bad -lv value escape as an exception with a stack trace; a small ScalarInputConverter throws CommandException(..., 64) instead, which CliFx prints as a one-line error.
  • CliFx's SystemConsole opens the raw standard handles and bypasses Console.SetOut; the host passes a FakeConsole over two tiny write-only streams that hand the bytes back to Console.Out/Console.Error (this is also what lets the in-process tests see the output). Ctrl+C registration is lost; the tool never registered one.
  • No hidden options exist in CliFx: the developer switch --generate-diagrammer-json-only is taken out of the arguments by the rewriter and set on the command directly.

Behavioral differences left (none pinned by the contract)

  • Multi-letter aliases cannot appear in the help's option column (only in the description text).
  • Help layout follows CliFx (USAGE/DESCRIPTION/PARAMETERS/OPTIONS, Default: <false>. on every flag, a Choices: list for --languageversion, the Remarks/Examples under DESCRIPTION, the version in the header); the README block was regenerated from the Release build.
  • Long option names are matched case-insensitively by CliFx once they reach it; the rewriter itself is exact.

AOT

CliFx 3 binds through generated typed descriptors, so <TrimmerRootAssembly Include="ilspycmd" /> and the McMaster value-parser warm-up are gone. IlcTreatWarningsAsErrors=false stays for Mono.Cecil. Not run locally: the Native AOT publish. The IlspyCmdRidPackages CI job (win-x64/linux-x64 publish plus smoke test) is the gate for this PR.

Assisted-by: Claude:claude-fable-5-1:Claude Code

The command-line libraries evaluated as McMaster replacements differ
from it in exactly these spots: a bare -usepdb must not consume the
following token, a multi-value option takes one value per occurrence
so the next bare token stays an input file, and -pd is not -p -d.
None of them was pinned by the existing contract tests.

Assisted-by: Claude:claude-fable-5-1:Claude Code
McMaster binds options by reflection, which is what the trimmer root
and the value-parser warm-up in the csproj existed for, and its hosting
integration pulled in Microsoft.Extensions.Hosting for an environment
object nothing read. CliFx 3 is source-generated and AOT-clean, but
its parser is position-based: input files are read only up to the
first option, an option collects every following bare token, every
single-dash token is split into one-letter options, and ':' or '=' do
not attach a value. The verb-less command line needs the opposite of
each, so a rewriter in front of CliFx reorders the tokens into the
form it parses and rejects what it would misread; CliFx then does the
typed binding, help and version.

The engine keeps its options as plain properties on ILSpyCmdProgram
and validates their combination itself; LegacyCommand (CliFx's default
command) and LegacyArgumentRewriter are the only files that know the
parser, so verbs can be added as named commands later and the
verb-less form can be retired by deleting those two. CliFx answers its
own usage errors with exit code 1, which this tool never used, so the
host maps 1 to EX_USAGE; its console opens the raw standard handles,
so the host routes it through Console.Out and Console.Error instead.

Assisted-by: Claude:claude-fable-5-1:Claude Code
Taken from the Release build's --help output, so the Debug-only ILAst
options stay out of it.

Assisted-by: Claude:claude-fable-5-1:Claude Code

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant