Skip to content

Commit 6f925e2

Browse files
committed
Multimodule handling
1 parent 9aa6e78 commit 6f925e2

6 files changed

Lines changed: 698 additions & 96 deletions

File tree

src/liberty/devCommands.ts

Lines changed: 147 additions & 46 deletions
Large diffs are not rendered by default.

src/liberty/libertyProject.ts

Lines changed: 191 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ export class ProjectProvider implements vscode.TreeDataProvider<LibertyProject>
2828

2929
// Map of buildFilePath -> LibertyProject
3030
private projects: Map<string, LibertyProject> = new Map();
31+
32+
// Root projects for hierarchical tree view
33+
private rootProjects: LibertyProject[] = [];
3134

3235
private _context: vscode.ExtensionContext;
3336

@@ -253,7 +256,104 @@ export class ProjectProvider implements vscode.TreeDataProvider<LibertyProject>
253256
}
254257

255258
public getTreeItem(element: LibertyProject): vscode.TreeItem {
256-
return element;
259+
// Set collapsible state based on whether project has children
260+
const collapsibleState = (element.isAggregator && element.children.length > 0)
261+
? vscode.TreeItemCollapsibleState.Collapsed
262+
: vscode.TreeItemCollapsibleState.None;
263+
264+
// Update the element's collapsible state by creating a new TreeItem
265+
const treeItem = new vscode.TreeItem(element.label, collapsibleState);
266+
treeItem.tooltip = element.tooltip;
267+
treeItem.iconPath = element.iconPath;
268+
treeItem.contextValue = element.contextValue;
269+
treeItem.command = element.command;
270+
271+
return treeItem;
272+
}
273+
274+
/**
275+
* Check if a project has any Liberty-enabled descendants
276+
* @param project The project to check
277+
* @returns true if project or any descendant is Liberty-enabled
278+
*/
279+
private hasLibertyDescendants(project: LibertyProject): boolean {
280+
if (project.isLibertyEnabled) {
281+
return true;
282+
}
283+
return project.children.some(child => this.hasLibertyDescendants(child));
284+
}
285+
286+
/**
287+
* Find all Liberty-enabled descendants of a project
288+
* @param project The project to search
289+
* @returns Array of Liberty-enabled descendant projects
290+
*/
291+
public findLibertyDescendants(project: LibertyProject): LibertyProject[] {
292+
const descendants: LibertyProject[] = [];
293+
294+
// Only search children - don't include the project itself
295+
// This ensures aggregators aren't included in their own descendant list
296+
for (const child of project.children) {
297+
// Add the child if it's Liberty-enabled
298+
if (child.isLibertyEnabled) {
299+
descendants.push(child);
300+
}
301+
// Recursively search the child's descendants
302+
descendants.push(...this.findLibertyDescendants(child));
303+
}
304+
305+
return descendants;
306+
}
307+
308+
/**
309+
* Execute a command on a project, with delegation to children if it's an aggregator
310+
* @param project The project to execute the command on
311+
* @param commandName The command name for display purposes
312+
* @returns The target project to execute on, or undefined if cancelled
313+
*/
314+
public async resolveCommandTarget(project: LibertyProject, commandName: string): Promise<LibertyProject | undefined> {
315+
// Check if project is an aggregator FIRST (before checking if Liberty-enabled)
316+
// An aggregator with children should delegate to children, not execute directly
317+
if (project.isAggregator && project.children.length > 0) {
318+
const libertyChildren = this.findLibertyDescendants(project);
319+
320+
if (libertyChildren.length === 0) {
321+
vscode.window.showWarningMessage(
322+
localize("no.liberty.modules.found", `No Liberty-enabled modules found in ${project.label}`)
323+
);
324+
return undefined;
325+
}
326+
327+
if (libertyChildren.length === 1) {
328+
// Only one Liberty child, execute automatically
329+
return libertyChildren[0];
330+
}
331+
332+
// Multiple children - show notification with buttons
333+
const buttonLabels = libertyChildren.map(c => c.label);
334+
const selected = await vscode.window.showInformationMessage(
335+
localize("select.module.for.command", commandName),
336+
...buttonLabels
337+
);
338+
339+
if (!selected) {
340+
return undefined;
341+
}
342+
343+
// Find the project that matches the selected button
344+
return libertyChildren.find(c => c.label === selected);
345+
}
346+
347+
// If we get here, check if it's a Liberty-enabled leaf project (not an aggregator)
348+
if (project.isLibertyEnabled) {
349+
return project;
350+
}
351+
352+
// Not Liberty-enabled and not an aggregator
353+
vscode.window.showWarningMessage(
354+
localize("project.not.liberty.enabled", `${project.label} is not Liberty-enabled`)
355+
);
356+
return undefined;
257357
}
258358

259359
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -264,12 +364,17 @@ export class ProjectProvider implements vscode.TreeDataProvider<LibertyProject>
264364
}
265365
// if element is null, vscode is asking for the root node
266366
if (element === undefined) {
267-
// projects is a map of buildFilePath -> LibertyProjects
268-
// Need to return an array of just the LibertyProject
367+
// Return root projects for hierarchical view
368+
// If no hierarchy is built, fall back to flat list
369+
if (this.rootProjects.length > 0) {
370+
return this.rootProjects;
371+
}
269372
return [... this.projects.values()];
270373
}
271-
// else it is asking for a child node
272-
return [];
374+
// Return children that are Liberty-enabled OR have Liberty descendants
375+
return element.children.filter(child =>
376+
child.isLibertyEnabled || this.hasLibertyDescendants(child)
377+
);
273378
}
274379

275380

@@ -401,6 +506,63 @@ export class ProjectProvider implements vscode.TreeDataProvider<LibertyProject>
401506
}
402507
return added;
403508
}
509+
/**
510+
* Build hierarchical relationships between projects based on metadata
511+
* @param projectsMap Map of all projects
512+
*/
513+
private async buildHierarchy(projectsMap: Map<string, LibertyProject>): Promise<void> {
514+
const mavenProjectsByArtifactId = new Map<string, LibertyProject>();
515+
const gradleProjectsByName = new Map<string, LibertyProject>();
516+
517+
// First pass: populate metadata for all projects
518+
for (const [buildFilePath, project] of projectsMap.entries()) {
519+
try {
520+
if (buildFilePath.endsWith("pom.xml")) {
521+
const xmlString = await fse.readFile(buildFilePath, "utf8");
522+
const metadata = await mavenUtil.extractMavenMetadata(buildFilePath, xmlString);
523+
project.artifactId = metadata.artifactId;
524+
project.parentArtifactId = metadata.parentArtifactId;
525+
project.isAggregator = metadata.isAggregator;
526+
project.isLibertyEnabled = metadata.isLibertyEnabled;
527+
mavenProjectsByArtifactId.set(metadata.artifactId, project);
528+
} else if (buildFilePath.endsWith("build.gradle")) {
529+
const metadata = await gradleUtil.extractGradleMetadata(buildFilePath);
530+
project.artifactId = metadata.projectName;
531+
project.parentArtifactId = metadata.parentProjectName;
532+
project.isAggregator = metadata.isAggregator;
533+
project.isLibertyEnabled = metadata.isLibertyEnabled;
534+
gradleProjectsByName.set(metadata.projectName, project);
535+
}
536+
} catch (error) {
537+
console.error(`Error extracting metadata for ${buildFilePath}:`, error);
538+
// Set defaults if metadata extraction fails
539+
project.isLibertyEnabled = true; // Assume Liberty-enabled if in the map
540+
}
541+
}
542+
543+
// Second pass: build parent-child relationships
544+
for (const project of projectsMap.values()) {
545+
if (project.parentArtifactId) {
546+
// Look up parent in the appropriate map based on build tool
547+
const parent = project.path.endsWith("pom.xml")
548+
? mavenProjectsByArtifactId.get(project.parentArtifactId)
549+
: gradleProjectsByName.get(project.parentArtifactId);
550+
551+
if (parent) {
552+
project.parent = parent;
553+
if (!parent.children.includes(project)) {
554+
parent.children.push(project);
555+
}
556+
}
557+
}
558+
}
559+
560+
// Third pass: identify root projects (no parent or parent not in workspace)
561+
this.rootProjects = Array.from(projectsMap.values())
562+
.filter(p => !p.parent)
563+
.filter(p => p.isLibertyEnabled || this.hasLibertyDescendants(p));
564+
}
565+
404566

405567
private async updateProjects(): Promise<void> {
406568
// find all build files in the open workspace and find all the ones that are valid for dev-mode
@@ -425,12 +587,14 @@ export class ProjectProvider implements vscode.TreeDataProvider<LibertyProject>
425587
}
426588

427589
for (const gradleBuild of validGradleBuilds) {
590+
console.log(`[DEBUG] Processing Gradle build: ${gradleBuild.getBuildFilePath()}, type: ${gradleBuild.getProjectType()}`);
428591
// if a LibertyProject for this build.gradle has already been created
429592
// we want to re-use it
430593
if ( !await this.addExistingProjectToNewProjectsMap(gradleBuild.getBuildFilePath(), gradleBuild.getProjectType(),
431594
newProjectsMap) ) {
432595
const project = await createProject(this._context, gradleBuild.getBuildFilePath(), gradleBuild.getProjectType());
433596
newProjectsMap.set(gradleBuild.getBuildFilePath(), project);
597+
console.log(`[DEBUG] Added Gradle project to map: ${gradleBuild.getBuildFilePath()}`);
434598
}
435599
}
436600

@@ -471,11 +635,32 @@ export class ProjectProvider implements vscode.TreeDataProvider<LibertyProject>
471635
}
472636
}
473637
}
638+
console.log(`[DEBUG] Total projects in map: ${newProjectsMap.size}`);
639+
for (const [path, proj] of newProjectsMap.entries()) {
640+
console.log(`[DEBUG] Project: ${proj.label} at ${path}`);
641+
}
642+
474643
this.projects = newProjectsMap;
644+
645+
// Build hierarchical relationships between projects
646+
await this.buildHierarchy(newProjectsMap);
647+
648+
console.log(`[DEBUG] Root projects after hierarchy: ${this.rootProjects.length}`);
649+
for (const root of this.rootProjects) {
650+
console.log(`[DEBUG] Root: ${root.label}, isAggregator: ${root.isAggregator}, isLibertyEnabled: ${root.isLibertyEnabled}, children: ${root.children.length}`);
651+
}
475652
}
476653
}
477654

478655
export class LibertyProject extends vscode.TreeItem {
656+
// New fields for multi-module hierarchy support
657+
public parent?: LibertyProject;
658+
public children: LibertyProject[] = [];
659+
public isAggregator: boolean = false;
660+
public isLibertyEnabled: boolean = false;
661+
public artifactId: string = "";
662+
public parentArtifactId?: string;
663+
479664
constructor(
480665
private _context: vscode.ExtensionContext,
481666
public label: string,
@@ -491,6 +676,7 @@ export class LibertyProject extends vscode.TreeItem {
491676
) {
492677
super(label, collapsibleState);
493678
this.tooltip = this.path;
679+
this.children = [];
494680
}
495681

496682
private EXPLORER_ICON = this.setExplorerIcon();

src/locales/en.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,5 +72,14 @@
7272
"hotkey.commands.title.add.project": "Liberty: Add Project to Liberty Tools",
7373
"hotkey.commands.title.remove.project": "Liberty: Remove Project from Liberty Tools",
7474
"hotkey.commands.title.show.commands": "Liberty: Show Liberty commands",
75-
"workspace.not.saved.projects.may.not.persist": "Save the workspace first. Projects that you manually add to Liberty Tools might not persist in the next VS Code session if you don't save the workspace before you add the project."
75+
"workspace.not.saved.projects.may.not.persist": "Save the workspace first. Projects that you manually add to Liberty Tools might not persist in the next VS Code session if you don't save the workspace before you add the project.",
76+
"no.liberty.modules.found": "No Liberty-enabled modules found in {0}",
77+
"select.module.for.command": "Select which module to {0}",
78+
"project.not.liberty.enabled": "{0} is not Liberty-enabled",
79+
"command.start.dev.mode": "start dev mode",
80+
"command.start.container.dev.mode": "start container dev mode",
81+
"command.start.custom.dev.mode": "start custom dev mode",
82+
"command.stop.dev.mode": "stop dev mode",
83+
"command.run.tests": "run tests",
84+
"command.attach.debugger": "attach debugger"
7685
}

0 commit comments

Comments
 (0)