Merge nucleic/olive-jade-civet-rznt into dev
This commit is contained in:
@@ -27,6 +27,14 @@ internal static class Program
|
||||
/// loading it by package name fails — which is exactly the first thing this tool found.
|
||||
private const string AssemblyName = "wslcsdkcs";
|
||||
|
||||
/// The API namespace. Filtering on it is not tidiness — it is correctness. A C#/WinRT
|
||||
/// projection also exports `ABI.Microsoft.WSL.Containers.*` marshalling plumbing whose types
|
||||
/// have the SAME short names (`Session`, `Container`, `ProcessSettings`), and `ABI.` sorts
|
||||
/// first. Matching assumptions by short name therefore checked every member against the
|
||||
/// marshalling struct and reported 42 false MISSINGs, with `CreateMarshaler` offered as the
|
||||
/// nearest name — which is the tell.
|
||||
private const string ApiNamespace = "Microsoft.WSL.Containers";
|
||||
|
||||
private static int Main(string[] args)
|
||||
{
|
||||
var probe = args.Contains("--probe") || args.Contains("--session");
|
||||
@@ -46,29 +54,65 @@ internal static class Program
|
||||
return 2;
|
||||
}
|
||||
|
||||
var types = assembly.GetExportedTypes().OrderBy(t => t.FullName, StringComparer.Ordinal).ToArray();
|
||||
Console.WriteLine($"{AssemblyName} {assembly.GetName().Version} — {types.Length} public types");
|
||||
var exported = assembly.GetExportedTypes().OrderBy(t => t.FullName, StringComparer.Ordinal).ToArray();
|
||||
var types = exported.Where(t => t.Namespace == ApiNamespace).ToArray();
|
||||
Console.WriteLine(
|
||||
$"{AssemblyName} {assembly.GetName().Version} — {types.Length} types in {ApiNamespace} "
|
||||
+ $"({exported.Length - types.Length} more are ABI/marshalling plumbing)");
|
||||
Console.WriteLine();
|
||||
|
||||
var report = new StringBuilder();
|
||||
report.AppendLine($"# {AssemblyName} {assembly.GetName().Version}");
|
||||
report.AppendLine($"# location: {assembly.Location}");
|
||||
report.AppendLine();
|
||||
foreach (var type in types) DescribeType(type, report);
|
||||
// The dump defaults to the API namespace for the same reason the checks do; `--all-types`
|
||||
// includes the ABI plumbing for when the projection itself is what's being debugged.
|
||||
foreach (var type in args.Contains("--all-types") ? exported : types) DescribeType(type, report);
|
||||
File.WriteAllText(outPath, report.ToString());
|
||||
Console.WriteLine($"full API dump → {Path.GetFullPath(outPath)}");
|
||||
Console.WriteLine();
|
||||
|
||||
var missing = CheckAssumptions(types);
|
||||
|
||||
if (probe) ProbeStatics(types);
|
||||
if (session) ProbeSession(types, ArgValue(args, "--session-name") ?? "nucleic-spike");
|
||||
// Components are probed BEFORE anything else that touches the service. On a machine
|
||||
// where WSL container support isn't installed, every live call fails with
|
||||
// REGDB_E_CLASSNOTREG (0x80040154) — "Class not registered" — and reporting that as a
|
||||
// string of mysterious COM errors would bury the one fact that explains them all.
|
||||
var componentsMissing = probe ? ProbeStatics(types) : null;
|
||||
if (session)
|
||||
{
|
||||
if (componentsMissing is { Count: > 0 })
|
||||
{
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("skipping --session: the components above are missing, so creating "
|
||||
+ "a session would only fail with the same 'class not registered'.");
|
||||
}
|
||||
else
|
||||
{
|
||||
ProbeSession(types, ArgValue(args, "--session-name") ?? "nucleic-spike");
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine(missing == 0
|
||||
? "RESULT: every WslcFacade assumption is present. Fix nothing; write the typed spike."
|
||||
: $"RESULT: {missing} assumption(s) wrong — each one is a line to change in "
|
||||
+ "windows/NucleicBroker/Wslc/WslcFacade.cs, and nowhere else (that is what IWslc is for).");
|
||||
if (componentsMissing is { Count: > 0 })
|
||||
{
|
||||
Console.WriteLine($"RESULT: this machine cannot run wslc yet — missing "
|
||||
+ $"{string.Join(", ", componentsMissing)}. Install with `wsl --install` (the "
|
||||
+ "Virtual Machine Platform component needs a reboot), then run this again.");
|
||||
Console.WriteLine(" This is the §8 onboarding condition, not a defect.");
|
||||
}
|
||||
else if (missing == 0)
|
||||
{
|
||||
Console.WriteLine("RESULT: every WslcFacade assumption is present — the package matches "
|
||||
+ "the surface recorded in docs/WINDOWS_PORT.md §13.1.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"RESULT: {missing} assumption(s) wrong. The recorded surface is 2.9.3, "
|
||||
+ "so this means the package MOVED: reconcile §13.1, then fix "
|
||||
+ "windows/NucleicBroker/Wslc/WslcFacade.cs — and nowhere else (that is what IWslc "
|
||||
+ "is for).");
|
||||
}
|
||||
// Exit 0 either way: a mismatch is this tool's PRODUCT, not its failure. Only a genuinely
|
||||
// broken run (assembly missing) is non-zero, so a wrapper script can tell them apart.
|
||||
return 0;
|
||||
@@ -174,7 +218,8 @@ internal static class Program
|
||||
wrong++;
|
||||
Console.WriteLine($" MISSING {type.Name}.{assumption.Member} "
|
||||
+ $"({assumption.MemberKind.ToString().ToLowerInvariant()})");
|
||||
Console.WriteLine($" why: {assumption.Why}");
|
||||
foreach (var line in Wrap(assumption.Why, 94))
|
||||
Console.WriteLine($" {line}");
|
||||
var near = Nearest(assumption.Member, MemberNames(type));
|
||||
if (near.Length > 0)
|
||||
Console.WriteLine($" nearest: {string.Join(", ", near)}");
|
||||
@@ -202,6 +247,28 @@ internal static class Program
|
||||
/// <summary>Cheap "did they just rename it" hint: shared prefix or containment, no edit
|
||||
/// distance. A three-name shortlist is enough to spot <c>HostGateway</c> vs
|
||||
/// <c>HostGatewayAddress</c>, which is the realistic failure mode.</summary>
|
||||
/// Hard-wrap so a long rationale can't be mangled into an unreadable fragment by the
|
||||
/// console. First line is prefixed "why:", continuations are indented under it.
|
||||
private static IEnumerable<string> Wrap(string text, int width)
|
||||
{
|
||||
var words = text.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
var line = new StringBuilder("why: ");
|
||||
var any = false;
|
||||
foreach (var word in words)
|
||||
{
|
||||
if (line.Length + word.Length + 1 > width && any)
|
||||
{
|
||||
yield return line.ToString();
|
||||
line = new StringBuilder(" ");
|
||||
any = false;
|
||||
}
|
||||
if (any) line.Append(' ');
|
||||
line.Append(word);
|
||||
any = true;
|
||||
}
|
||||
if (any) yield return line.ToString();
|
||||
}
|
||||
|
||||
private static string[] Nearest(string wanted, IEnumerable<string> candidates)
|
||||
{
|
||||
var needle = wanted.TrimStart('.');
|
||||
@@ -229,14 +296,17 @@ internal static class Program
|
||||
/// Names come from the real 2.9.3 surface (`GetVersion`, `GetMissingComponents`) — the
|
||||
/// documented `GetServiceVersion`/`ComponentFlags` shapes in Microsoft Learn's sample are
|
||||
/// stale against the shipped package (docs/WINDOWS_PORT.md §13.1).
|
||||
private static void ProbeStatics(Type[] types)
|
||||
private static IReadOnlyList<string>? ProbeStatics(Type[] types)
|
||||
{
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("live probe (read-only):");
|
||||
var service = types.FirstOrDefault(t => t.Name == "WslcService");
|
||||
if (service is null) { Console.WriteLine(" no WslcService type — skipping"); return; }
|
||||
if (service is null) { Console.WriteLine(" no WslcService type — skipping"); return null; }
|
||||
|
||||
foreach (var name in new[] { "GetVersion", "GetMissingComponents" })
|
||||
List<string>? componentsMissing = null;
|
||||
// GetMissingComponents first: it answers from OS feature state and works even when the
|
||||
// service class isn't registered, so it is the call that EXPLAINS the others.
|
||||
foreach (var name in new[] { "GetMissingComponents", "GetVersion" })
|
||||
{
|
||||
var method = service.GetMethods(Public)
|
||||
.FirstOrDefault(m => m.IsStatic && m.Name == name && m.GetParameters().Length == 0);
|
||||
@@ -248,16 +318,26 @@ internal static class Program
|
||||
}
|
||||
try
|
||||
{
|
||||
Console.WriteLine($" {name}() = {Render(method.Invoke(null, null))}");
|
||||
var value = method.Invoke(null, null);
|
||||
Console.WriteLine($" {name}() = {Render(value)}");
|
||||
if (name == "GetMissingComponents" && value is System.Collections.IEnumerable list)
|
||||
{
|
||||
componentsMissing = list.Cast<object?>().Select(v => v?.ToString() ?? "?").ToList();
|
||||
if (componentsMissing.Count > 0)
|
||||
Console.WriteLine(" → WSL container support is NOT installed here. Every "
|
||||
+ "call below that reaches the service will fail with 0x80040154.");
|
||||
}
|
||||
}
|
||||
catch (TargetInvocationException e)
|
||||
{
|
||||
// The interesting failure: WSL not installed. That IS the onboarding condition
|
||||
// §8 step 2 exists to handle, so report it plainly rather than as a crash.
|
||||
Console.WriteLine($" {name}() threw {Describe(e.InnerException)}");
|
||||
var inner = e.InnerException;
|
||||
var expected = componentsMissing is { Count: > 0 }
|
||||
&& inner is COMException { HResult: unchecked((int)0x80040154) };
|
||||
Console.WriteLine($" {name}() threw {Describe(inner)}"
|
||||
+ (expected ? " ← expected: the components above are missing" : ""));
|
||||
}
|
||||
}
|
||||
Console.WriteLine(" (a non-empty GetMissingComponents is onboarding work, not a failure — §8)");
|
||||
return componentsMissing;
|
||||
}
|
||||
|
||||
/// Create a real session, then create a SECOND one with the same name.
|
||||
|
||||
Reference in New Issue
Block a user