feat(gradle): link and unlink independent Gradle builds (#1413)

Opening a folder of independent Gradle builds selected only the first build. Link Gradle Projects… (pick a build's settings or build file) and Unlink on a build's root let an ordinary project window manage several independent builds without changing their scripts. The build model holds a list of Gradle builds; each keeps its own wrapper, working directory, environment, task routing and sync cache, keyed by the owning build directory. The links persist in the project's Gradle settings (linkedProjects); unlinking leaves files and saved run configurations alone. Existing includeBuild support is unchanged, and an explicitly linked build takes precedence over a composite that also includes it. Workspace windows keep Link/Unlink disabled.

Co-authored-by: gudcks0305 <[email protected]>
Цей коміт міститься в:
yoo hyeong chan authored and GitHub committed 2026-11-05 11:23:39 +08:00
1 джерело 682fee633d
коміт 0dfdbbc323
43 files changed
+1846 -254

No files matched your search

+5 -1
Переглянути файл
@@ -103,6 +103,7 @@ export interface LanguagesValid {
jdtlsJdk: string;
jdtlsVmArgs: string;
gradle: {
linkedProjects: string[] | null;
jvm: string;
distribution: "wrapper" | "local";
home: string;
@@ -160,7 +161,7 @@ export const DEFAULT_LANGUAGES: LanguagesValid = {
jdk: "",
jdtlsJdk: "",
jdtlsVmArgs: "",
gradle: { jvm: "", distribution: "wrapper", home: "", userHome: "", runUsing: "gradle" },
gradle: { linkedProjects: null, jvm: "", distribution: "wrapper", home: "", userHome: "", runUsing: "gradle" },
maven: { home: "", userSettings: "", localRepository: "", unlinkedProjects: [], daemon: false, mvndHome: "" },
},
markdown: { previewFontSize: null },
@@ -292,6 +293,9 @@ export function validateLanguages(raw: unknown): LanguagesValid {
jdtlsJdk: text(java.jdtlsJdk, d.java.jdtlsJdk),
jdtlsVmArgs: text(java.jdtlsVmArgs, d.java.jdtlsVmArgs),
gradle: {
linkedProjects: Array.isArray(gradle.linkedProjects)
? gradle.linkedProjects.filter((p): p is string => typeof p === "string")
: null,
jvm: text(gradle.jvm, d.java.gradle.jvm),
distribution: oneOf(gradle.distribution, ["wrapper", "local"] as const, d.java.gradle.distribution),
home: text(gradle.home, d.java.gradle.home),
+5
Переглянути файл
@@ -1,4 +1,9 @@
{
"Link Gradle Projects": "关联 Gradle 项目",
"Unlink Gradle Project": "取消关联 Gradle 项目",
"Unlink Gradle project?": "取消关联 Gradle 项目?",
"Remove this build from Gradle? Files will remain in the Project view; link its build file again to restore it.": "从 Gradle 中移除此构建?文件将保留在项目视图中;重新关联其构建文件即可恢复。",
"Gradle build files": "Gradle 构建文件",
"Unity": "Unity 项目",
"Show Meta Files": "显示 Meta 文件",
"Show Generated Files": "显示生成的文件",
+30 -10
Переглянути файл
@@ -2,7 +2,7 @@ import { describe, it, expect } from "vitest";
import { buildPresentation, isModuleFolder, moduleFolderIcon } from "./projectPresentation";
import type { BuildModel } from "./ipc";
const model = (partial: unknown) => ({ maven: null, gradle: null, ...(partial as object) }) as BuildModel;
const model = (partial: unknown) => ({ maven: null, gradle: [], ...(partial as object) }) as BuildModel;
describe("build presentation context", () => {
it.each(["maven", "gradle"] as const)("uses distinct %s root and nested module glyphs", (tool) => {
const modules = new Map([
@@ -45,12 +45,14 @@ describe("build presentation context", () => {
it("includes Gradle root and nested project directories", () => {
const context = buildPresentation(
model({
gradle: {
projects: [
{ dir: "", tasks: [] },
{ dir: "lib/core", tasks: [] },
],
},
gradle: [
{
projects: [
{ dir: "", tasks: [] },
{ dir: "lib/core", tasks: [] },
],
},
],
}),
);
expect([...context.modules]).toEqual([
@@ -67,9 +69,27 @@ describe("build presentation context", () => {
}),
).spring,
).toBe(true);
expect(buildPresentation(model({ gradle: { projects: [{ dir: "", tasks: [{ name: "bootRun" }] }] } })).spring).toBe(
true,
);
expect(
buildPresentation(model({ gradle: [{ projects: [{ dir: "", tasks: [{ name: "bootRun" }] }] }] })).spring,
).toBe(true);
});
it("keeps plain folders plain", () => expect(buildPresentation(model({})).modules.size).toBe(0));
});
it("includes modules and Spring tasks from every independently linked Gradle build", () => {
const context = buildPresentation(
model({
gradle: [
{
projects: [
{ dir: "first", tasks: [] },
{ dir: "first/app", tasks: [] },
],
},
{ projects: [{ dir: "second", tasks: [{ name: "bootRun" }] }] },
],
}),
);
expect([...context.modules.keys()]).toEqual(["first", "first/app", "second"]);
expect(context.spring).toBe(true);
});
+2 -2
Переглянути файл
@@ -14,13 +14,13 @@ export const useBuildPresentation = create<BuildPresentation>(() => EMPTY);
export function buildPresentation(model: BuildModel): BuildPresentation {
const modules = new Map<string, "maven" | "gradle">();
for (const p of model.maven?.projects ?? []) modules.set(p.dir, "maven");
for (const p of model.gradle?.projects ?? []) modules.set(p.dir, "gradle");
for (const p of (model.gradle ?? []).flatMap((g) => g.projects)) modules.set(p.dir, "gradle");
const spring =
(model.maven?.projects ?? []).some(
(p) =>
p.dependencies.some((d) => d.groupId.startsWith("org.springframework")) ||
p.plugins.some((p) => p.groupId.startsWith("org.springframework")),
) || (model.gradle?.projects ?? []).some((p) => p.tasks.some((t) => t.name === "bootRun"));
) || (model.gradle ?? []).some((g) => g.projects.some((p) => p.tasks.some((t) => t.name === "bootRun")));
return { modules, spring };
}