mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-06 08:07:49 +00:00
fix(run): run PHP files with the interpreter chosen in Settings
Running a .php file (or a #!/usr/bin/env php script) produced a configuration with the PATH php: the file producer looked tools up on PATH, and the settings pass only replaces a bare "php" command, never the absolute path the producer had resolved. With php@8.1 selected in Settings and php 8.4 on PATH, the file ran on 8.4, and with no php on PATH it failed to start although an interpreter was selected. A new sdk_php_effective command returns the configured interpreter, the way sdk_python_effective does for Python, and the producer prefers it. The not-found message now also points to the PHP settings page.
This commit is contained in:
@@ -52,6 +52,14 @@ pub fn sdk_python_effective(host: &Host, window: &str) -> IdeResult<Option<ide_c
|
||||
Ok(ide_sdk::apply::python_interpreter(&project.root, &project.settings.effective()).map(Into::into))
|
||||
}
|
||||
|
||||
/// The PHP interpreter chosen in Settings › Languages & Frameworks › PHP; `None`: the `php` on `PATH`.
|
||||
pub fn sdk_php_effective(host: &Host, window: &str) -> IdeResult<Option<ide_core::IpcPath>> {
|
||||
host.require_trust(window)?;
|
||||
let project = host.project(window)?;
|
||||
let settings = project.settings.effective();
|
||||
Ok(ide_sdk::apply::setting_path(&project.root, &settings.languages.php.interpreter).map(Into::into))
|
||||
}
|
||||
|
||||
/// The PHP language level of the project `path` belongs to (a workspace member, by path, as
|
||||
/// the language server configuration picks it).
|
||||
#[derive(Clone, Debug, serde::Serialize, specta::Type)]
|
||||
@@ -198,6 +206,7 @@ pub async fn sdk_python_pip(
|
||||
pub(crate) fn register(d: &mut Dispatcher) {
|
||||
d.add("sdk_detect", |c| async move { sdk_detect(&c.host, &c.window, c.arg("kind")?).await });
|
||||
d.add("sdk_python_effective", |c| async move { sdk_python_effective(&c.host, &c.window) });
|
||||
d.add("sdk_php_effective", |c| async move { sdk_php_effective(&c.host, &c.window) });
|
||||
d.add(
|
||||
"sdk_php_language_level",
|
||||
|c| async move { sdk_php_language_level(&c.host, &c.window, c.arg("path")?).await },
|
||||
|
||||
@@ -398,6 +398,7 @@ pub fn builder() -> tauri_specta::Builder<tauri::Wry> {
|
||||
tools::jdk_download,
|
||||
sdk::sdk_detect,
|
||||
sdk::sdk_python_effective,
|
||||
sdk::sdk_php_effective,
|
||||
sdk::sdk_php_language_level,
|
||||
sdk::sdk_project_libraries,
|
||||
sdk::sdk_locate_tools,
|
||||
|
||||
@@ -24,6 +24,13 @@ pub fn sdk_python_effective(window: Window, state: State<'_, AppState>) -> IdeRe
|
||||
ide_host::ops::sdk::sdk_python_effective(&state.host, window.label())
|
||||
}
|
||||
|
||||
/// The PHP interpreter chosen in Settings › Languages & Frameworks › PHP; `None`: the `php` on `PATH`.
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub fn sdk_php_effective(window: Window, state: State<'_, AppState>) -> IdeResult<Option<ide_core::IpcPath>> {
|
||||
ide_host::ops::sdk::sdk_php_effective(&state.host, window.label())
|
||||
}
|
||||
|
||||
/// The PHP language level of the project `path` belongs to: the effective one and Auto's.
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
|
||||
@@ -772,6 +772,8 @@ export const commands = {
|
||||
sdkDetect: (kind: SdkKind) => typedError<Sdk[], IdeError>(__TAURI_INVOKE("sdk_detect", { kind })),
|
||||
/** The interpreter the project uses: the configured one, else its virtualenv. */
|
||||
sdkPythonEffective: () => typedError<string | null, IdeError>(__TAURI_INVOKE("sdk_python_effective")),
|
||||
/** The PHP interpreter chosen in Settings › Languages & Frameworks › PHP; `None`: the `php` on `PATH`. */
|
||||
sdkPhpEffective: () => typedError<string | null, IdeError>(__TAURI_INVOKE("sdk_php_effective")),
|
||||
/** The PHP language level of the project `path` belongs to: the effective one and Auto's. */
|
||||
sdkPhpLanguageLevel: (path: string) => typedError<PhpLanguageLevels, IdeError>(__TAURI_INVOKE("sdk_php_language_level", { path })),
|
||||
/**
|
||||
|
||||
@@ -98,7 +98,13 @@ export async function produceResolved(input: ContextInput, dir = false): Promise
|
||||
// Project configurations (tests, cargo, gradle…) need no runtime lookup.
|
||||
if (named.config && !named.config.id.startsWith("context:file:")) return named;
|
||||
const lang = scriptLanguage(input.path, input.content);
|
||||
const env = await toolEnv(neededTools(fresh), input.path, fresh.root, lang === "python" || lang === "shebang");
|
||||
const env = await toolEnv(
|
||||
neededTools(fresh),
|
||||
input.path,
|
||||
fresh.root,
|
||||
lang === "python" || lang === "shebang",
|
||||
lang === "php" || lang === "shebang",
|
||||
);
|
||||
return produceContext(fresh, env);
|
||||
}
|
||||
|
||||
|
||||
@@ -226,6 +226,15 @@ describe("single-file producers (scratches)", () => {
|
||||
it("Ruby, PHP, Perl, Lua, R, Swift, Dart, Elixir, Scala CLI, Kotlin script", () => {
|
||||
expect(run(scratch("a.rb"))).toMatchObject({ command: "/bin/ruby", args: ["a.rb"] });
|
||||
expect(run(scratch("a.php"))).toMatchObject({ command: "/bin/php", args: ["a.php"] });
|
||||
// The PHP chosen in Settings wins over the PATH one, also with no PHP on PATH (#1555).
|
||||
const php81 = "/opt/homebrew/opt/php@8.1/bin/php";
|
||||
expect(run(scratch("a.php"), { ...ALL, php: php81 })).toMatchObject({ command: php81, args: ["a.php"] });
|
||||
expect(run(scratch("a.php"), env({}, { php: php81 }))).toMatchObject({ command: php81, args: ["a.php"] });
|
||||
expect(run(scratch("tool", "#!/usr/bin/env php\n<?php echo 1;"), { ...ALL, php: php81 })).toMatchObject({
|
||||
command: php81,
|
||||
args: ["tool"],
|
||||
});
|
||||
expect(produceContext(scratch("a.php"), env({}))!.missing!.body).toMatch(/Settings › Languages & Frameworks › PHP/);
|
||||
expect(run(scratch("a.pl"))).toMatchObject({ command: "/bin/perl" });
|
||||
expect(run(scratch("a.lua"))).toMatchObject({ command: "/bin/lua" });
|
||||
expect(run(scratch("a.R"))).toMatchObject({ command: "/bin/Rscript", args: ["a.R"] });
|
||||
|
||||
@@ -38,6 +38,8 @@ export interface ToolEnv {
|
||||
windows?: boolean;
|
||||
/** The project interpreter from settings (Python). */
|
||||
python?: string | null;
|
||||
/** The PHP interpreter chosen in settings (else the `php` on `PATH`). */
|
||||
php?: string | null;
|
||||
/** The project root has `deno.json(c)` / `bun.lock(b)`. */
|
||||
deno?: boolean;
|
||||
bun?: boolean;
|
||||
@@ -288,7 +290,7 @@ const INSTALL: Record<string, string> = {
|
||||
js: "Install Node.js from https://nodejs.org (or `brew install node`).",
|
||||
python: "Install Python (`brew install python`) or choose an interpreter in Settings › Python Interpreter.",
|
||||
ruby: "Install Ruby (`brew install ruby`).",
|
||||
php: "Install PHP (`brew install php`).",
|
||||
php: "Install PHP (`brew install php`) or choose an interpreter in Settings › Languages & Frameworks › PHP.",
|
||||
perl: "Install Perl (`brew install perl`).",
|
||||
lua: "Install Lua (`brew install lua`).",
|
||||
r: "Install R (`brew install r`) so that `Rscript` is on the PATH.",
|
||||
@@ -762,6 +764,8 @@ export function produceFile(input: ContextInput, env: ToolEnv): Produced | null
|
||||
config: fileConfig(s, "python", py.command, [...py.prefix, s.arg], "debugpy"),
|
||||
};
|
||||
}
|
||||
if (interp === "php" && env.php)
|
||||
return { name: s.file, scope: "file", config: fileConfig(s, "php", env.php, [...sb.slice(1), s.arg]) };
|
||||
const t = sb[0]!.startsWith("/") ? { command: sb[0]!, name: interp } : pick(env, [interp]);
|
||||
if (!t) return { name: s.file, scope: "file", missing: missing(lang, s.file, [interp]) };
|
||||
const kind = /^(ba|z|k|da|fi)?sh$/.test(interp) ? "shell" : interp === "node" ? "node" : interp;
|
||||
@@ -805,6 +809,8 @@ export function produceFile(input: ContextInput, env: ToolEnv): Produced | null
|
||||
// Sources roots go on the load path (`-Ilib`), as `rake test` does; paths are project-relative.
|
||||
return simple(lang, s, env, lang, () => [...(s.inProject ? rubyLoadPath(currentRoots()) : []), s.arg]);
|
||||
case "php":
|
||||
if (env.php) return { name: s.file, scope: "file", config: fileConfig(s, "php", env.php, [s.arg]) };
|
||||
return simple(lang, s, env, lang, () => [s.arg]);
|
||||
case "perl":
|
||||
case "lua":
|
||||
case "swift":
|
||||
|
||||
@@ -86,6 +86,14 @@ async function projectPython(): Promise<string | null> {
|
||||
}
|
||||
}
|
||||
|
||||
async function projectPhp(): Promise<string | null> {
|
||||
try {
|
||||
return await call(commands.sdkPhpEffective());
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function rootMarkers(root: string | null): Promise<{ deno: boolean; bun: boolean }> {
|
||||
if (!root) return { deno: false, bun: false };
|
||||
try {
|
||||
@@ -106,10 +114,12 @@ export async function toolEnv(
|
||||
file: string | null,
|
||||
root: string | null,
|
||||
python: boolean,
|
||||
php = false,
|
||||
): Promise<ToolEnv> {
|
||||
const [tools, py, markers] = await Promise.all([
|
||||
const [tools, py, phpInterpreter, markers] = await Promise.all([
|
||||
locateTools(names, file),
|
||||
python ? projectPython() : Promise.resolve(null),
|
||||
php ? projectPhp() : Promise.resolve(null),
|
||||
names.includes("deno") || names.includes("bun") ? rootMarkers(root) : Promise.resolve({ deno: false, bun: false }),
|
||||
]);
|
||||
return {
|
||||
@@ -117,6 +127,7 @@ export async function toolEnv(
|
||||
compileDir: compileDir ?? "/tmp/openidea-run",
|
||||
windows: isWindows(),
|
||||
python: py,
|
||||
php: phpInterpreter,
|
||||
...markers,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user