дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-11 04:05:39 +02:00
Порівняти коміти
1
Коміти
| Автор | SHA1 | Дата | |
|---|---|---|---|
|
|
504bbdf259 |
No files matched your search
@@ -130,6 +130,17 @@ pub fn run_system_env(_host: &Host, _window: &str) -> std::collections::BTreeMap
|
||||
ide_run::system_env()
|
||||
}
|
||||
|
||||
/// Variables of the `.env` files a debug launch names, in order (later files win):
|
||||
/// the same files a run loads, over the system environment debug adapters inherit.
|
||||
pub fn run_env_files(
|
||||
_host: &Host,
|
||||
_window: &str,
|
||||
files: Vec<std::path::PathBuf>,
|
||||
) -> IdeResult<std::collections::BTreeMap<String, String>> {
|
||||
ide_run::envfile::load(&files, &ide_run::system_env())
|
||||
.map_err(|e| IdeError::not_found(format!("cannot read .env file: {e}")))
|
||||
}
|
||||
|
||||
/// Writes the Gradle init script that adds `jvm_args` to the JVMs the build forks
|
||||
/// (`bootRun`, `run`, tests) and returns its path, for `--init-script`.
|
||||
pub async fn run_gradle_jvm_args_script(_host: &Host, _window: &str, jvm_args: Vec<String>) -> IdeResult<String> {
|
||||
@@ -332,6 +343,7 @@ pub(crate) fn register(d: &mut Dispatcher) {
|
||||
run_import_configs(&c.host, &c.window, c.arg("source")?, c.arg("existing")?)
|
||||
});
|
||||
d.add("run_system_env", |c| async move { Ok(run_system_env(&c.host, &c.window)) });
|
||||
d.add("run_env_files", |c| async move { run_env_files(&c.host, &c.window, c.arg("files")?) });
|
||||
d.add("run_gradle_jvm_args_script", |c| async move {
|
||||
run_gradle_jvm_args_script(&c.host, &c.window, c.arg("jvmArgs")?).await
|
||||
});
|
||||
|
||||
@@ -157,3 +157,22 @@ async fn gradle_init_scripts_are_written_by_the_host_that_runs_the_build() {
|
||||
let escaped = ops::run::run_gradle_init_script(&host, "main", "../escape".into(), String::new()).await;
|
||||
assert!(escaped.is_err(), "{escaped:?}");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn debug_env_files_load_in_order_like_a_run() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let host = host(tmp.path());
|
||||
let first = tmp.path().join("first.env");
|
||||
let second = tmp.path().join("second.env");
|
||||
std::fs::write(&first, "SHARED=one\nONLY_FIRST=1\nREF=${SHARED}-suffix\n").unwrap();
|
||||
std::fs::write(&second, "SHARED=two\nONLY_SECOND=2\n").unwrap();
|
||||
let vars = ops::run::run_env_files(&host, "main", vec![first, second]).unwrap();
|
||||
// Later files win; references resolve against earlier files, then the system environment.
|
||||
assert_eq!(vars.get("SHARED").map(String::as_str), Some("two"));
|
||||
assert_eq!(vars.get("ONLY_FIRST").map(String::as_str), Some("1"));
|
||||
assert_eq!(vars.get("ONLY_SECOND").map(String::as_str), Some("2"));
|
||||
assert_eq!(vars.get("REF").map(String::as_str), Some("one-suffix"));
|
||||
// An unreadable file is an error, like a run that names a missing `.env`.
|
||||
let missing = ops::run::run_env_files(&host, "main", vec![tmp.path().join("nope.env")]);
|
||||
assert!(missing.is_err(), "{missing:?}");
|
||||
}
|
||||
@@ -370,6 +370,7 @@ pub fn builder() -> tauri_specta::Builder<tauri::Wry> {
|
||||
run::run_import_sources,
|
||||
run::run_import_configs,
|
||||
run::run_system_env,
|
||||
run::run_env_files,
|
||||
run::run_start,
|
||||
run::run_stop,
|
||||
run::run_stdin,
|
||||
|
||||
@@ -179,6 +179,17 @@ pub fn run_system_env(state: State<'_, AppState>) -> std::collections::BTreeMap<
|
||||
ide_host::ops::run::run_system_env(&state.host, "")
|
||||
}
|
||||
|
||||
/// Variables of the `.env` files a debug launch names, for the debugger to start with.
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub fn run_env_files(
|
||||
window: Window,
|
||||
state: State<'_, AppState>,
|
||||
files: Vec<std::path::PathBuf>,
|
||||
) -> IdeResult<std::collections::BTreeMap<String, String>> {
|
||||
ide_host::ops::run::run_env_files(&state.host, window.label(), files)
|
||||
}
|
||||
|
||||
/// Writes the Gradle init script adding `jvm_args` to the JVMs the build forks; returns its path.
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
|
||||
@@ -708,6 +708,8 @@ export const commands = {
|
||||
runImportConfigs: (source: RunImportSource, existing: RunConfig_Deserialize[]) => typedError<RunImport_Serialize, IdeError>(__TAURI_INVOKE("run_import_configs", { source, existing })),
|
||||
/** The environment runs inherit when "Include system environment variables" is on. */
|
||||
runSystemEnv: () => __TAURI_INVOKE<{ [key in string]: string }>("run_system_env"),
|
||||
/** Variables of the `.env` files a debug launch names, for the debugger to start with. */
|
||||
runEnvFiles: (files: string[]) => typedError<{ [key in string]: string }, IdeError>(__TAURI_INVOKE("run_env_files", { files })),
|
||||
runStart: (config: RunConfig_Deserialize, onOutput: Channel<OutputChunk>, request: number | null) => typedError<RunInfo, IdeError>(__TAURI_INVOKE("run_start", { config, onOutput, request })),
|
||||
runStop: (id: RunId) => typedError<null, IdeError>(__TAURI_INVOKE("run_stop", { id })),
|
||||
runStdin: (id: RunId, data: string) => typedError<null, IdeError>(__TAURI_INVOKE("run_stdin", { id, data })),
|
||||
|
||||
@@ -1,7 +1,18 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { RunConfig } from "@/platform/ipc";
|
||||
import { usePlatform } from "@/platform/platformInfo";
|
||||
import { debugUnsupportedReason, launchConfigFor } from "../launch";
|
||||
import { debugUnsupportedReason, launchConfigFor, withEnvFiles } from "../launch";
|
||||
|
||||
const ipc = vi.hoisted(() => {
|
||||
const ok = <T>(data: T) => Promise.resolve({ status: "ok" as const, data });
|
||||
return { ok, runEnvFiles: vi.fn((_files: string[]) => ok({} as Record<string, string>)) };
|
||||
});
|
||||
|
||||
vi.mock("@/platform/ipc", async (importOriginal) => {
|
||||
const real = await importOriginal<typeof import("@/platform/ipc")>();
|
||||
const { ok: _ok, ...cmds } = ipc;
|
||||
return { ...real, commands: { ...real.commands, ...cmds } };
|
||||
});
|
||||
|
||||
const rc = (p: Partial<RunConfig>): RunConfig => ({
|
||||
id: "x",
|
||||
@@ -20,7 +31,10 @@ const rc = (p: Partial<RunConfig>): RunConfig => ({
|
||||
const extra = (l: { extraJson: string | null } | null) => JSON.parse(l?.extraJson ?? "{}");
|
||||
|
||||
const originalPlatform = usePlatform.getState();
|
||||
beforeEach(() => usePlatform.setState({ os: "linux" }));
|
||||
beforeEach(() => {
|
||||
usePlatform.setState({ os: "linux" });
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
afterEach(() => usePlatform.setState(originalPlatform));
|
||||
|
||||
describe("run config → launch config", () => {
|
||||
@@ -484,3 +498,18 @@ it("raw and typed Go debug configurations inherit custom tags unless explicitly
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
describe("withEnvFiles", () => {
|
||||
it("leaves configurations without .env files alone (no backend call)", async () => {
|
||||
const config = rc({ env: { A: "1" } });
|
||||
await expect(withEnvFiles(config, "/p")).resolves.toBe(config);
|
||||
expect(ipc.runEnvFiles).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("loads .env files under the explicit variables, resolved against the project root", async () => {
|
||||
ipc.runEnvFiles.mockResolvedValueOnce({ status: "ok", data: { FROM_FILE: "file", A: "file" } });
|
||||
const out = await withEnvFiles(rc({ env: { A: "explicit" }, envFiles: [".env", "env/local.env"] }), "/p");
|
||||
expect(ipc.runEnvFiles).toHaveBeenCalledWith(["/p/.env", "/p/env/local.env"]);
|
||||
expect(out.env).toEqual({ FROM_FILE: "file", A: "explicit" });
|
||||
});
|
||||
});
|
||||
@@ -31,7 +31,7 @@ import { fieldAt, methodAt } from "./breakpoints/symbols";
|
||||
import { toggleBreakpointSmart } from "./breakpoints/toggle";
|
||||
import { isJavaAttachTarget, isJavaDebuggable, javaAttachConfig, javaLaunchConfig } from "./java";
|
||||
import { cancelHotSwapBuild, reloadChangedClasses } from "./hotswap";
|
||||
import { debugUnsupportedReason, launchConfigFor } from "./launch";
|
||||
import { debugUnsupportedReason, launchConfigFor, withEnvFiles } from "./launch";
|
||||
import {
|
||||
isPythonAttachTarget,
|
||||
isRemoteConfig,
|
||||
@@ -331,7 +331,7 @@ async function startConfig(arg: unknown) {
|
||||
// php-debug starts the PHP chosen in Settings unless the command names one.
|
||||
const php =
|
||||
config.debugAdapter === "php-debug" && isTauri() ? await call(commands.sdkPhpEffective()).catch(() => null) : null;
|
||||
const launch = launchConfigFor(prepared, root, php);
|
||||
const launch = launchConfigFor(await withEnvFiles(prepared, root), root, php);
|
||||
if (!launch) {
|
||||
notifications().notify({
|
||||
group: "run",
|
||||
@@ -417,7 +417,7 @@ export function registerDebugActions(): () => void {
|
||||
const config = await call(commands.pluginPrepareDebugRun(a.config));
|
||||
const root = useProject.getState().project?.root;
|
||||
if (!root) throw new Error("No project");
|
||||
const launch = launchConfigFor(config, root);
|
||||
const launch = launchConfigFor(await withEnvFiles(config, root), root);
|
||||
if (!launch) throw new Error("This configuration has no debugger");
|
||||
let id: number | null = null;
|
||||
let stopped = false;
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
type JavaPreparation,
|
||||
} from "@/platform/javaLaunch";
|
||||
import { maybeEditorService, notifications } from "@/workbench/contributions";
|
||||
import { withEnvFiles } from "./launch";
|
||||
|
||||
export const JAVA_ADAPTER = "java-debug";
|
||||
export const KOTLIN_ADAPTER = "kotlin-debug-adapter";
|
||||
@@ -111,6 +112,7 @@ export async function javaLaunchConfig(
|
||||
preparation?: JavaPreparation,
|
||||
): Promise<LaunchConfig> {
|
||||
const cwd = rc.cwd ? join(root, rc.cwd) : root;
|
||||
rc = await withEnvFiles(rc, root);
|
||||
const parsed = parseJavaArgs(rc.args);
|
||||
if (!parsed.mainClass) throw new Error(`No main class in '${rc.name}'`);
|
||||
preparation?.signal.throwIfAborted();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { parseArgs, formatArgs } from "@/platform/commandLine";
|
||||
/** Run configuration → DAP launch configuration, per adapter. */
|
||||
import { workflowSettings } from "@/platform/workflowSettings";
|
||||
import type { LaunchConfig, RunConfig } from "@/platform/ipc";
|
||||
import { call, commands, type LaunchConfig, type RunConfig } from "@/platform/ipc";
|
||||
import { hostOs } from "@/platform/remote";
|
||||
|
||||
function isAbsolute(p: string) {
|
||||
@@ -37,6 +37,18 @@ function base(rc: RunConfig, adapter: string, cwd: string): LaunchConfig {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* `rc` with its `.env` files loaded underneath its explicit variables (mirrors Run):
|
||||
* Debug used to drop them because `base` only copies `env`. Absolute paths go to the
|
||||
* backend, which parses with the same loader runs use.
|
||||
*/
|
||||
export async function withEnvFiles(rc: RunConfig, root: string): Promise<RunConfig> {
|
||||
const files = rc.envFiles?.filter((f) => f.trim()) ?? [];
|
||||
if (!files.length) return rc;
|
||||
const loaded = await call(commands.runEnvFiles(files.map((f) => join(root, f))));
|
||||
return { ...rc, env: { ...loaded, ...rc.env } };
|
||||
}
|
||||
|
||||
function cargoLaunch(rc: RunConfig, cwd: string, adapter: string): LaunchConfig {
|
||||
const [cargoArgs, programArgs] = splitAtDashDash(rc.args);
|
||||
const [sub, ...rest] = cargoArgs;
|
||||
|
||||
Reference in new issue
Заблокувати користувача