mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-07 18:38:33 -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);
|
||||
return;
|
||||
}
|
||||
// A polyglot repository: Go modules one level down (`api/go.mod`).
|
||||
for dir in subdirs(root) {
|
||||
// Match Maven's shallow discovery without walking every source directory.
|
||||
let mut pending = vec![(root.to_path_buf(), 0)];
|
||||
while let Some((dir, depth)) = pending.pop() {
|
||||
if dir.join("go.mod").is_file() {
|
||||
let rel = relative(root, &dir);
|
||||
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"]);
|
||||
}
|
||||
|
||||
#[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]
|
||||
fn python_projects() {
|
||||
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", () => {
|
||||
expect(contextConfig("src/bin/server.rs", configs)!.id).toBe("cargo:run:server");
|
||||
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 { phpunitArgs } from "./testing/phpunit";
|
||||
import { goTestParts } from "./goArguments";
|
||||
import { goBuildType } from "./goTypes";
|
||||
import { isWindows } from "./jvmTypes";
|
||||
|
||||
export type FileRole = "test" | "main" | "script" | null;
|
||||
@@ -51,12 +52,17 @@ function testKinds(relPath: string): string[] {
|
||||
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). */
|
||||
function allClosest(configs: RunConfig[], relPath: string): RunConfig[] {
|
||||
const score = (c: RunConfig) => {
|
||||
const cwd = (c.kind === "go" ? (c.params?.module ?? c.cwd ?? "") : (c.cwd ?? ""))
|
||||
.replace(/^\.\/?/, "")
|
||||
.replace(/\/+$/, "");
|
||||
const cwd = configScope(c);
|
||||
if (!cwd) return 0;
|
||||
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`. */
|
||||
export function relToCwd(relPath: string, config: RunConfig): string {
|
||||
const cwd = (config.kind === "go" ? (config.params?.module ?? config.cwd ?? "") : (config.cwd ?? ""))
|
||||
.replace(/^\.\/?/, "")
|
||||
.replace(/\/+$/, "");
|
||||
const cwd = configScope(config);
|
||||
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));
|
||||
const best = allClosest(candidates, relPath);
|
||||
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] : [];
|
||||
}
|
||||
|
||||
@@ -218,16 +222,33 @@ function mainConfigFor(relPath: string, configs: RunConfig[]): RunConfig | null
|
||||
return byBin ?? closest(runs, relPath) ?? runs[0] ?? null;
|
||||
}
|
||||
if (relPath.endsWith(".go")) {
|
||||
const gos = configs.filter((c) => c.kind === "go");
|
||||
const runs = gos.filter((c) => c.args[0] === "run");
|
||||
const gos = allClosest(
|
||||
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).
|
||||
const base = closest(runs, relPath) ?? closest(gos, relPath) ?? runs[0] ?? gos[0];
|
||||
const base = runs[0] ?? gos[0];
|
||||
if (!base) return null;
|
||||
const dir = dirOf(relToCwd(relPath, base));
|
||||
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;
|
||||
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)) {
|
||||
// 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"] });
|
||||
});
|
||||
|
||||
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", () => {
|
||||
const p = produceContext(scratch("hihi.rs", 'fn main() {\n println!("hi");\n}'), ALL)!;
|
||||
expect(p.name).toBe("hihi.rs");
|
||||
|
||||
@@ -16,7 +16,7 @@ import { applicationConfig, jvmModuleDir } from "./application";
|
||||
import { formatArgs } from "./configs";
|
||||
import { goGenerateConfig } from "./goGenerate";
|
||||
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 { firstTopLevelType, scanJvmTargets, simpleClassName } from "./jvmTargets";
|
||||
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[] {
|
||||
return configs.filter((c) => {
|
||||
if (!kinds.includes(c.kind)) return false;
|
||||
const cwd = (c.cwd ?? "").replace(/^\.\/?/, "").replace(/\/+$/, "");
|
||||
if (!kinds.includes(c.kind) || c.id.startsWith("context:")) return false;
|
||||
const cwd = configScope(c);
|
||||
return !cwd || rel.startsWith(cwd + "/");
|
||||
});
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user