mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-13 17:37:06 -10:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae558d70f1 |
No files matched your search
@@ -712,11 +712,14 @@ fn detect_go(root: &Path, out: &mut Vec<RunConfig>) {
|
|||||||
detect_go_module(root, None, out);
|
detect_go_module(root, None, out);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// A polyglot repository: Go modules one level down (`api/go.mod`).
|
// Match Maven's shallow discovery without walking every source directory.
|
||||||
for dir in subdirs(root) {
|
let mut pending = vec![(root.to_path_buf(), 0)];
|
||||||
|
while let Some((dir, depth)) = pending.pop() {
|
||||||
if dir.join("go.mod").is_file() {
|
if dir.join("go.mod").is_file() {
|
||||||
let rel = relative(root, &dir);
|
let rel = relative(root, &dir);
|
||||||
detect_go_module(&dir, Some(rel), out);
|
detect_go_module(&dir, Some(rel), out);
|
||||||
|
} else if depth < 3 {
|
||||||
|
pending.extend(subdirs(&dir).into_iter().rev().map(|child| (child, depth + 1)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -362,6 +362,48 @@ fn go_projects() {
|
|||||||
assert_eq!(ids(&detect(dir.path())), ["go:test"]);
|
assert_eq!(ids(&detect(dir.path())), ["go:test"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nested_go_modules_keep_their_working_directories() {
|
||||||
|
let dir = project(&[
|
||||||
|
("Cargo.toml", "[package]\nname = 'workspace'\n"),
|
||||||
|
("fixtures/go-app/go.mod", "module example.com/openidea/notes\n"),
|
||||||
|
("fixtures/go-app/cmd/server/main.go", "package main\nfunc main() {}\n"),
|
||||||
|
("services/backend/api/go.mod", "module example.com/api\n"),
|
||||||
|
("services/backend/api/main.go", "package main\nfunc main() {}\n"),
|
||||||
|
("fixtures/go-app/nested/go.mod", "module ignored\n"),
|
||||||
|
("vendor/ignored/go.mod", "module ignored\n"),
|
||||||
|
("node_modules/ignored/go.mod", "module ignored\n"),
|
||||||
|
("target/ignored/go.mod", "module ignored\n"),
|
||||||
|
("build/ignored/go.mod", "module ignored\n"),
|
||||||
|
("dist/ignored/go.mod", "module ignored\n"),
|
||||||
|
(".hidden/ignored/go.mod", "module ignored\n"),
|
||||||
|
]);
|
||||||
|
let configs = detect(dir.path());
|
||||||
|
let go: Vec<_> = configs.iter().filter(|c| c.kind == "go").map(|c| c.id.as_str()).collect();
|
||||||
|
assert_eq!(
|
||||||
|
go,
|
||||||
|
[
|
||||||
|
"go:run:server:fixtures/go-app",
|
||||||
|
"go:test:fixtures/go-app",
|
||||||
|
"go:run:services/backend/api",
|
||||||
|
"go:test:services/backend/api",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
let run = find(&configs, "go:run:server:fixtures/go-app");
|
||||||
|
assert_eq!(cmdline(run), "go run ./cmd/server");
|
||||||
|
assert_eq!(run.cwd.as_deref(), Some(Path::new("fixtures/go-app")));
|
||||||
|
let test = find(&configs, "go:test:services/backend/api");
|
||||||
|
assert_eq!(test.cwd.as_deref(), Some(Path::new("services/backend/api")));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
#[test]
|
||||||
|
fn nested_go_discovery_does_not_follow_symlinks() {
|
||||||
|
let dir = project(&[("real/go.mod", "module real\n")]);
|
||||||
|
std::os::unix::fs::symlink(dir.path().join("real"), dir.path().join("alias")).unwrap();
|
||||||
|
assert_eq!(ids(&detect(dir.path())), ["go:test:real"]);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn python_projects() {
|
fn python_projects() {
|
||||||
let dir = project(&[
|
let dir = project(&[
|
||||||
|
|||||||
@@ -235,6 +235,43 @@ describe("context configuration", () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("does not borrow another Go module's run or test configuration", () => {
|
||||||
|
const go = [
|
||||||
|
cfg({ id: "go:run:api", kind: "go", command: "go", args: ["run", "."], cwd: "api" }),
|
||||||
|
cfg({ id: "go:test:api", kind: "go", command: "go", args: ["test", "./..."], cwd: "api", isTest: true }),
|
||||||
|
];
|
||||||
|
expect(contextConfig("other/main.go", go, "package main\nfunc main() {}")).toBeNull();
|
||||||
|
expect(contextConfig("other/main_test.go", go, "package main")).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses a nested Go module even when only the parent has a run configuration", () => {
|
||||||
|
const go = [
|
||||||
|
cfg({ id: "go:run", kind: "go", command: "go", args: ["run", "."] }),
|
||||||
|
cfg({ id: "go:test:api", kind: "go", command: "go", args: ["test", "./..."], cwd: "api", isTest: true }),
|
||||||
|
];
|
||||||
|
expect(contextConfig("api/tool/main.go", go, "package main\nfunc main() {}")).toMatchObject({
|
||||||
|
cwd: "api",
|
||||||
|
args: ["run", "./tool"],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not pass typed Go test flags to an application build", () => {
|
||||||
|
const go = cfg({
|
||||||
|
id: "user:go-test",
|
||||||
|
kind: "go",
|
||||||
|
command: "go",
|
||||||
|
args: ["test", "-count=1", "./..."],
|
||||||
|
isTest: true,
|
||||||
|
params: { goKind: "test", module: "api", toolArgs: "-count=1" },
|
||||||
|
});
|
||||||
|
expect(contextConfig("api/cmd/server/main.go", [go], "package main\nfunc main() {}")).toMatchObject({
|
||||||
|
cwd: "api",
|
||||||
|
args: ["run", "./cmd/server"],
|
||||||
|
params: undefined,
|
||||||
|
isTest: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("finds run targets and script invocations", () => {
|
it("finds run targets and script invocations", () => {
|
||||||
expect(contextConfig("src/bin/server.rs", configs)!.id).toBe("cargo:run:server");
|
expect(contextConfig("src/bin/server.rs", configs)!.id).toBe("cargo:run:server");
|
||||||
expect(contextConfig("tools/gen.py", configs)).toMatchObject({
|
expect(contextConfig("tools/gen.py", configs)).toMatchObject({
|
||||||
|
|||||||
+33
-12
@@ -7,6 +7,7 @@ import type { RunConfig } from "@/platform/ipc";
|
|||||||
import { djangoLabel, djangoTestsOf, hasDjangoTests } from "./djangoTypes";
|
import { djangoLabel, djangoTestsOf, hasDjangoTests } from "./djangoTypes";
|
||||||
import { phpunitArgs } from "./testing/phpunit";
|
import { phpunitArgs } from "./testing/phpunit";
|
||||||
import { goTestParts } from "./goArguments";
|
import { goTestParts } from "./goArguments";
|
||||||
|
import { goBuildType } from "./goTypes";
|
||||||
import { isWindows } from "./jvmTypes";
|
import { isWindows } from "./jvmTypes";
|
||||||
|
|
||||||
export type FileRole = "test" | "main" | "script" | null;
|
export type FileRole = "test" | "main" | "script" | null;
|
||||||
@@ -51,12 +52,17 @@ function testKinds(relPath: string): string[] {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The module owning a configuration's targets, relative to the project root. */
|
||||||
|
export function configScope(config: RunConfig): string {
|
||||||
|
return (config.kind === "go" ? (config.params?.module ?? config.cwd ?? "") : (config.cwd ?? ""))
|
||||||
|
.replace(/^\.\/?/, "")
|
||||||
|
.replace(/\/+$/, "");
|
||||||
|
}
|
||||||
|
|
||||||
/** Configs whose working directory most specifically contains `relPath` (ties in their original order). */
|
/** Configs whose working directory most specifically contains `relPath` (ties in their original order). */
|
||||||
function allClosest(configs: RunConfig[], relPath: string): RunConfig[] {
|
function allClosest(configs: RunConfig[], relPath: string): RunConfig[] {
|
||||||
const score = (c: RunConfig) => {
|
const score = (c: RunConfig) => {
|
||||||
const cwd = (c.kind === "go" ? (c.params?.module ?? c.cwd ?? "") : (c.cwd ?? ""))
|
const cwd = configScope(c);
|
||||||
.replace(/^\.\/?/, "")
|
|
||||||
.replace(/\/+$/, "");
|
|
||||||
if (!cwd) return 0;
|
if (!cwd) return 0;
|
||||||
return relPath === cwd || relPath.startsWith(cwd + "/") ? cwd.length : -1;
|
return relPath === cwd || relPath.startsWith(cwd + "/") ? cwd.length : -1;
|
||||||
};
|
};
|
||||||
@@ -72,9 +78,7 @@ function closest(configs: RunConfig[], relPath: string): RunConfig | undefined {
|
|||||||
|
|
||||||
/** `relPath` relative to the working directory of `config`. */
|
/** `relPath` relative to the working directory of `config`. */
|
||||||
export function relToCwd(relPath: string, config: RunConfig): string {
|
export function relToCwd(relPath: string, config: RunConfig): string {
|
||||||
const cwd = (config.kind === "go" ? (config.params?.module ?? config.cwd ?? "") : (config.cwd ?? ""))
|
const cwd = configScope(config);
|
||||||
.replace(/^\.\/?/, "")
|
|
||||||
.replace(/\/+$/, "");
|
|
||||||
return cwd && relPath.startsWith(cwd + "/") ? relPath.slice(cwd.length + 1) : relPath;
|
return cwd && relPath.startsWith(cwd + "/") ? relPath.slice(cwd.length + 1) : relPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,7 +151,7 @@ export function testBasesFor(relPath: string, configs: RunConfig[]): RunConfig[]
|
|||||||
.sort((a, b) => kinds.indexOf(a.kind) - kinds.indexOf(b.kind));
|
.sort((a, b) => kinds.indexOf(a.kind) - kinds.indexOf(b.kind));
|
||||||
const best = allClosest(candidates, relPath);
|
const best = allClosest(candidates, relPath);
|
||||||
if (best.length) return best;
|
if (best.length) return best;
|
||||||
const fallback = candidates[0] ?? implicitPytest(relPath, configs);
|
const fallback = (kinds.includes("go") ? undefined : candidates[0]) ?? implicitPytest(relPath, configs);
|
||||||
return fallback ? [fallback] : [];
|
return fallback ? [fallback] : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,16 +222,33 @@ function mainConfigFor(relPath: string, configs: RunConfig[]): RunConfig | null
|
|||||||
return byBin ?? closest(runs, relPath) ?? runs[0] ?? null;
|
return byBin ?? closest(runs, relPath) ?? runs[0] ?? null;
|
||||||
}
|
}
|
||||||
if (relPath.endsWith(".go")) {
|
if (relPath.endsWith(".go")) {
|
||||||
const gos = configs.filter((c) => c.kind === "go");
|
const gos = allClosest(
|
||||||
const runs = gos.filter((c) => c.args[0] === "run");
|
configs.filter((c) => c.kind === "go" && !c.id.startsWith("context:")),
|
||||||
|
relPath,
|
||||||
|
);
|
||||||
|
const runs = gos.filter((c) => c.args[0] === "run" || c.params?.goKind === "build");
|
||||||
// The module containing the file (nested modules run from their own directory).
|
// The module containing the file (nested modules run from their own directory).
|
||||||
const base = closest(runs, relPath) ?? closest(gos, relPath) ?? runs[0] ?? gos[0];
|
const base = runs[0] ?? gos[0];
|
||||||
if (!base) return null;
|
if (!base) return null;
|
||||||
const dir = dirOf(relToCwd(relPath, base));
|
const dir = dirOf(relToCwd(relPath, base));
|
||||||
const target = dir ? `./${dir}` : ".";
|
const target = dir ? `./${dir}` : ".";
|
||||||
const hit = runs.find((c) => (c.cwd ?? "") === (base.cwd ?? "") && c.args[1] === target);
|
if (base.params?.goKind === "build") {
|
||||||
|
const params = { ...base.params, goKind: "build", scope: "package", target, runAfterBuild: "true" };
|
||||||
|
return derived(base, relPath, {
|
||||||
|
...goBuildType.commandLine(params),
|
||||||
|
params,
|
||||||
|
name: `go run ${target}`,
|
||||||
|
isTest: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const hit = runs.find((c) => configScope(c) === configScope(base) && c.args[1] === target);
|
||||||
if (hit) return hit;
|
if (hit) return hit;
|
||||||
return derived(base, relPath, { args: ["run", target], name: `go run ${target}`, isTest: false });
|
return derived(base, relPath, {
|
||||||
|
args: ["run", target],
|
||||||
|
name: `go run ${target}`,
|
||||||
|
isTest: false,
|
||||||
|
...(base.params?.goKind === "test" ? { params: undefined, cwd: base.params.module ?? base.cwd } : {}),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (/\.(kt|java)$/.test(relPath)) {
|
if (/\.(kt|java)$/.test(relPath)) {
|
||||||
// The build containing the file (builds nested in a folder of repositories carry a `:<dir>` suffix).
|
// The build containing the file (builds nested in a folder of repositories carry a `:<dir>` suffix).
|
||||||
|
|||||||
@@ -273,6 +273,82 @@ describe("single-file producers (scratches)", () => {
|
|||||||
expect(p.config).toMatchObject({ args: ["run", "./cmd/tool"] });
|
expect(p.config).toMatchObject({ args: ["run", "./cmd/tool"] });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Go: nested modules win over previous single-file runs", () => {
|
||||||
|
const go = [
|
||||||
|
cfg({
|
||||||
|
id: "context:file:fixtures/go-app/cmd/server/main.go",
|
||||||
|
kind: "go",
|
||||||
|
args: ["run", "fixtures/go-app/cmd/server/main.go"],
|
||||||
|
}),
|
||||||
|
cfg({
|
||||||
|
id: "go:run:server:fixtures/go-app",
|
||||||
|
kind: "go",
|
||||||
|
args: ["run", "./cmd/server"],
|
||||||
|
cwd: "fixtures/go-app",
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
const input = {
|
||||||
|
path: `${ROOT}/fixtures/go-app/cmd/server/main.go`,
|
||||||
|
root: ROOT,
|
||||||
|
configs: go,
|
||||||
|
content: "package main\nfunc main() {}",
|
||||||
|
};
|
||||||
|
for (const line of [undefined, 1]) {
|
||||||
|
expect(produceContext({ ...input, line }, ALL)?.config).toMatchObject({
|
||||||
|
id: "go:run:server:fixtures/go-app",
|
||||||
|
args: ["run", "./cmd/server"],
|
||||||
|
cwd: "fixtures/go-app",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Go: single-file runs never become module configurations", () => {
|
||||||
|
const input = {
|
||||||
|
path: `${ROOT}/standalone/main.go`,
|
||||||
|
root: ROOT,
|
||||||
|
configs: [] as RunConfig[],
|
||||||
|
content: "package main\nfunc main() {}",
|
||||||
|
};
|
||||||
|
const first = produceContext(input, ALL)!;
|
||||||
|
expect(first.config).toMatchObject({ id: "context:file:standalone/main.go", args: ["run", "standalone/main.go"] });
|
||||||
|
expect(produceContext({ ...input, configs: [first.config!] }, ALL)?.config).toEqual(first.config);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Go: typed modules own only their own files", () => {
|
||||||
|
const configs = [
|
||||||
|
...["api", "worker"].map((module) =>
|
||||||
|
cfg({
|
||||||
|
id: `go:run:${module}`,
|
||||||
|
kind: "go",
|
||||||
|
args: ["build", "-race", "."],
|
||||||
|
params: { module, goKind: "build", scope: "package", target: ".", toolArgs: "-race" },
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
for (const module of ["api", "worker"]) {
|
||||||
|
expect(
|
||||||
|
produceContext(
|
||||||
|
{
|
||||||
|
path: `${ROOT}/${module}/cmd/server/main.go`,
|
||||||
|
root: ROOT,
|
||||||
|
configs,
|
||||||
|
content: "package main\nfunc main() {}",
|
||||||
|
},
|
||||||
|
ALL,
|
||||||
|
)?.config,
|
||||||
|
).toMatchObject({
|
||||||
|
args: ["build", "-race", "./cmd/server"],
|
||||||
|
params: { module, target: "./cmd/server", goKind: "build", runAfterBuild: "true" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
expect(
|
||||||
|
produceContext(
|
||||||
|
{ path: `${ROOT}/standalone/main.go`, root: ROOT, configs, content: "package main\nfunc main() {}" },
|
||||||
|
ALL,
|
||||||
|
)?.config,
|
||||||
|
).toMatchObject({ id: "context:file:standalone/main.go", args: ["run", "standalone/main.go"] });
|
||||||
|
});
|
||||||
|
|
||||||
it("Rust: rustc into the compile directory, then the binary; codelldb debugs the binary", () => {
|
it("Rust: rustc into the compile directory, then the binary; codelldb debugs the binary", () => {
|
||||||
const p = produceContext(scratch("hihi.rs", 'fn main() {\n println!("hi");\n}'), ALL)!;
|
const p = produceContext(scratch("hihi.rs", 'fn main() {\n println!("hi");\n}'), ALL)!;
|
||||||
expect(p.name).toBe("hihi.rs");
|
expect(p.name).toBe("hihi.rs");
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { applicationConfig, jvmModuleDir } from "./application";
|
|||||||
import { formatArgs } from "./configs";
|
import { formatArgs } from "./configs";
|
||||||
import { goGenerateConfig } from "./goGenerate";
|
import { goGenerateConfig } from "./goGenerate";
|
||||||
import { goTestParts } from "./goArguments";
|
import { goTestParts } from "./goArguments";
|
||||||
import { contextConfig, fileRole, projectPython, testBasesFor, testConfigFor } from "./context";
|
import { configScope, contextConfig, fileRole, projectPython, testBasesFor, testConfigFor } from "./context";
|
||||||
import { djangoLabel, djangoTestsOf, hasDjangoTests } from "./djangoTypes";
|
import { djangoLabel, djangoTestsOf, hasDjangoTests } from "./djangoTypes";
|
||||||
import { firstTopLevelType, scanJvmTargets, simpleClassName } from "./jvmTargets";
|
import { firstTopLevelType, scanJvmTargets, simpleClassName } from "./jvmTargets";
|
||||||
import { isWindows } from "./jvmTypes";
|
import { isWindows } from "./jvmTypes";
|
||||||
@@ -615,8 +615,8 @@ const hasKotlinMain = (content?: string) => content === undefined || /^(?:suspen
|
|||||||
*/
|
*/
|
||||||
function buildOwners(rel: string, configs: readonly RunConfig[], kinds: readonly string[]): RunConfig[] {
|
function buildOwners(rel: string, configs: readonly RunConfig[], kinds: readonly string[]): RunConfig[] {
|
||||||
return configs.filter((c) => {
|
return configs.filter((c) => {
|
||||||
if (!kinds.includes(c.kind)) return false;
|
if (!kinds.includes(c.kind) || c.id.startsWith("context:")) return false;
|
||||||
const cwd = (c.cwd ?? "").replace(/^\.\/?/, "").replace(/\/+$/, "");
|
const cwd = configScope(c);
|
||||||
return !cwd || rel.startsWith(cwd + "/");
|
return !cwd || rel.startsWith(cwd + "/");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user