Skip to content

Commit b11ce64

Browse files
committed
fix: xlings 发现以 mcpp self env 为权威来源(项目级契约),路径探测仅作回退
1 parent 9e1740c commit b11ce64

4 files changed

Lines changed: 69 additions & 4 deletions

File tree

src/cliController.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -630,7 +630,7 @@ export class McppCliController {
630630
return false;
631631
}
632632

633-
private mcppExecutable(project: McppProjectDiscovery | undefined): string {
633+
public mcppExecutable(project: McppProjectDiscovery | undefined): string {
634634
const uri = project === undefined
635635
? vscode.workspace.workspaceFolders?.[0]?.uri
636636
: vscode.Uri.file(project.root);

src/extension.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
type McppProjectDiscovery,
2020
} from "./discovery";
2121
import {
22-
findXlingsExecutable,
22+
resolveXlingsExecutable,
2323
llvmToolsVersionSpec,
2424
xlingsInstallArgs,
2525
} from "./llvmTools";
@@ -763,7 +763,9 @@ async function autoConfigureModulesWizard(
763763
: { stage: "clangd", state: "failed", detail: "clangd 配置未完成。" };
764764
}
765765

766-
const xlingsPath = findXlingsExecutable();
766+
const xlingsPath = await resolveXlingsExecutable(
767+
cliController.mcppExecutable(currentContext.project),
768+
);
767769
const compilerPath = currentContext.analysis.compilerPath;
768770
if (xlingsPath === undefined || compilerPath === undefined) {
769771
return {

src/llvmTools.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ import path from "node:path";
44
import process from "node:process";
55

66
import type { ToolIdentity } from "./analysis";
7-
import { runProcess, type ProcessResult } from "./process";
7+
import {
8+
runProcess,
9+
type ProcessResult,
10+
type ProcessRunner,
11+
} from "./process";
812

913
export function llvmToolsVersionSpec(identity: ToolIdentity): string {
1014
return `${identity.major}.${identity.minor}.${identity.patch}`;
@@ -131,6 +135,26 @@ function xlingsResolvableOnPath(pathValue?: string): boolean {
131135
return false;
132136
}
133137

138+
const XLINGS_BINARY_LINE = /^\s*xlings binary\s*=\s*(.+?)\s*$/im;
139+
140+
// Source of truth is mcpp itself, not the filesystem or PATH: `mcpp self env`
141+
// reports the exact xlings bundled with THIS mcpp (mcpp is a project-level
142+
// environment; it owns its tool paths). Works for install.sh, AUR and any
143+
// custom MCPP_PREFIX layout. Falls back to the historical path heuristics for
144+
// standalone ~/.xlings installs and for mcpp versions without the line.
145+
export async function resolveXlingsExecutable(
146+
mcppExecutable: string,
147+
runner: ProcessRunner = runProcess,
148+
): Promise<string | undefined> {
149+
const result = await runner(mcppExecutable, ["self", "env"]);
150+
const match = `${result.stdout}\n${result.stderr}`.match(XLINGS_BINARY_LINE);
151+
const reported = match?.[1]?.trim();
152+
if (reported !== undefined && reported.length > 0 && existsSync(reported)) {
153+
return reported;
154+
}
155+
return findXlingsExecutable();
156+
}
157+
134158
export async function runXlingsCommand(
135159
xlingsPath: string,
136160
args: string[],

test/llvmTools.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
xlingsInstallArgs,
1111
deriveInstalledClangdPath,
1212
findXlingsExecutable,
13+
resolveXlingsExecutable,
1314
} from "../src/llvmTools";
1415

1516
test("extracts version string from ToolIdentity", () => {
@@ -118,3 +119,41 @@ test("findXlingsExecutable honors MCPP_VENDORED_XLINGS", () => {
118119
rmSync(root, { recursive: true, force: true });
119120
}
120121
});
122+
123+
test("resolveXlingsExecutable reads the xlings binary from `mcpp self env`", async () => {
124+
const root = mkdtempSync(path.join(os.tmpdir(), "mcpp-vscode-selfenv-"));
125+
const xlingsPath = path.join(root, "registry", "bin", "xlings");
126+
mkdirSync(path.dirname(xlingsPath), { recursive: true });
127+
writeFileSync(xlingsPath, "#!/bin/sh\n");
128+
const runner = async () => ({
129+
exitCode: 0,
130+
stdout: `MCPP_HOME = ${root}\nxlings binary = ${xlingsPath}\nxlings pinned = 2026.8.8.1\n`,
131+
stderr: "",
132+
});
133+
try {
134+
assert.equal(
135+
await resolveXlingsExecutable("/tools/mcpp", runner),
136+
xlingsPath,
137+
);
138+
} finally {
139+
rmSync(root, { recursive: true, force: true });
140+
}
141+
});
142+
143+
test("resolveXlingsExecutable falls back when the reported path does not exist", async () => {
144+
const runner = async () => ({
145+
exitCode: 0,
146+
stdout: "xlings binary = /no/such/xlings\n",
147+
stderr: "",
148+
});
149+
const result = await resolveXlingsExecutable("/tools/mcpp", runner);
150+
// Fallback heuristics find nothing in this environment, so the result is
151+
// undefined unless a standalone ~/.xlings or PATH xlings happens to exist.
152+
assert.ok(result === undefined || typeof result === "string");
153+
});
154+
155+
test("resolveXlingsExecutable falls back when `mcpp self env` fails", async () => {
156+
const runner = async () => ({ exitCode: 1, stdout: "", stderr: "boom\n" });
157+
const result = await resolveXlingsExecutable("/tools/mcpp", runner);
158+
assert.ok(result === undefined || typeof result === "string");
159+
});

0 commit comments

Comments
 (0)