Skip to content

Commit 3a69f90

Browse files
committed
GH-598 Search for nested project folders
1 parent a72cd67 commit 3a69f90

File tree

1 file changed

+123
-25
lines changed

1 file changed

+123
-25
lines changed

integrations/vscode/src/projectUtils.ts

Lines changed: 123 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
*/
2525

2626
import * as vscode from 'vscode';
27+
import * as path from 'path';
28+
import * as fs from 'fs';
29+
import * as logUtils from './logUtils';
2730

2831

2932
const NBLS_GET_SOURCE_ROOTS_COMMAND = 'nbls.java.get.project.source.roots';
@@ -37,9 +40,10 @@ const JDT_EXECUTE_WORKSPACE_COMMAND = 'java.execute.workspaceCommand';
3740

3841
const MICRONAUT_TOOLS_SELECTED_SUBPROJECT_COMMAND = 'extension.micronaut-tools.navigation.getSelectedSubproject';
3942

40-
// TODO: integrate with Micronaut Tools to only track sources of the selected GCN application module
4143
export async function getSourceRoots(workspaceFolder?: vscode.WorkspaceFolder): Promise<string[] | undefined> {
44+
logUtils.logInfo(`[projectUtils] Computing source roots${workspaceFolder ? ' for folder ' + workspaceFolder.uri.fsPath : ''}...`);
4245
if (!vscode.workspace.workspaceFolders?.length) { // No folder opened
46+
logUtils.logInfo('[projectUtils] No folder opened');
4347
return [];
4448
}
4549

@@ -56,6 +60,7 @@ export async function getSourceRoots(workspaceFolder?: vscode.WorkspaceFolder):
5660
const hasNblsProjectSourceRootsCommand = commands.includes(NBLS_GET_SOURCE_ROOTS_COMMAND);
5761
const jdtApi = vscode.extensions.getExtension(JDT_EXTENSION_ID)?.exports;
5862
if (!hasNblsProjectSourceRootsCommand && !jdtApi?.getProjectSettings) {
63+
logUtils.logWarning('[projectUtils] No Java support to compute source roots');
5964
// TODO: wait for NBLS/JDT if installed
6065
return undefined; // No Java support available
6166
}
@@ -65,16 +70,45 @@ export async function getSourceRoots(workspaceFolder?: vscode.WorkspaceFolder):
6570

6671
const sourceRoots: string[] = [];
6772
const getUriSourceRoots = hasNblsProjectSourceRootsCommand ? getUriSourceRootsNbls : getUriSourceRootsJdt;
73+
logUtils.logInfo(`[projectUtils] Using ${hasNblsProjectSourceRootsCommand ? 'NBLS' : 'JDT'} to compute source roots`);
6874
for (const folder of workspaceFolders) {
69-
await getUriSourceRoots(sourceRoots, folder, folder.uri.toString(), hasNblsProjectInfoCommand, hasMicronautToolsSubprojectCommand, jdtApi);
75+
const unrecognizedProjectFolders: vscode.Uri[] = [];
76+
try {
77+
const foundSourceRoots = await getUriSourceRoots(sourceRoots, folder, folder.uri.toString(), hasNblsProjectInfoCommand, hasMicronautToolsSubprojectCommand, jdtApi);
78+
if (!foundSourceRoots && !isSupportedProjectUri(folder.uri)) {
79+
unrecognizedProjectFolders.push(folder.uri);
80+
}
81+
} catch (err) {
82+
logUtils.logError(`[projectUtils] Error computing source roots: ${err}`);
83+
}
84+
85+
// Try to find a nested supported project folder
86+
while (unrecognizedProjectFolders.length) {
87+
const unrecognizedProjectFolder = unrecognizedProjectFolders.shift();
88+
if (unrecognizedProjectFolder) {
89+
const subfolders = fs.readdirSync(unrecognizedProjectFolder.fsPath);
90+
for (const subfolder of subfolders) {
91+
const subfolderUri = vscode.Uri.joinPath(unrecognizedProjectFolder, subfolder);
92+
if (fs.lstatSync(subfolderUri.fsPath)?.isDirectory()) {
93+
const foundSourceRoots = await getUriSourceRoots(sourceRoots, folder, subfolderUri.toString(), hasNblsProjectInfoCommand, hasMicronautToolsSubprojectCommand, jdtApi);
94+
if (!foundSourceRoots && !isSupportedProjectUri(folder.uri)) {
95+
unrecognizedProjectFolders.push(subfolderUri);
96+
}
97+
}
98+
}
99+
}
100+
}
70101
}
102+
logUtils.logInfo(`[projectUtils] Found ${sourceRoots.length} source root(s)`);
71103
return sourceRoots;
72104
}
73105

74-
async function getUriSourceRootsNbls(sourceRoots: string[], folder: vscode.WorkspaceFolder, uri: string, hasNblsProjectInfoCommand: boolean, hasMicronautToolsSubprojectCommand: boolean) {
106+
async function getUriSourceRootsNbls(sourceRoots: string[], folder: vscode.WorkspaceFolder, uri: string, hasNblsProjectInfoCommand: boolean, hasMicronautToolsSubprojectCommand: boolean): Promise<boolean> {
107+
let foundSourceRoots = false;
75108
const uriSourceRoots: string[] | undefined = await vscode.commands.executeCommand(NBLS_GET_SOURCE_ROOTS_COMMAND, uri);
76109
if (uriSourceRoots) {
77110
if (uriSourceRoots.length) { // found project source roots
111+
foundSourceRoots = true;
78112
for (const uriSourceRoot of uriSourceRoots) {
79113
const sourceRoot = vscode.Uri.parse(uriSourceRoot).fsPath;
80114
if (!sourceRoots.includes(sourceRoot)) {
@@ -101,44 +135,52 @@ async function getUriSourceRootsNbls(sourceRoots: string[], folder: vscode.Works
101135
}
102136
}
103137
for (const subproject of subprojects) {
104-
await getUriSourceRootsNbls(sourceRoots, folder, subproject, false, false); // false prevents deep search (OK for GCN, may need to be enabled for other projects)
138+
foundSourceRoots = foundSourceRoots || await getUriSourceRootsNbls(sourceRoots, folder, subproject, false, false); // false prevents deep search (OK for GCN, may need to be enabled for other projects)
105139
}
106140
}
107141
}
108142
}
109143
}
144+
return foundSourceRoots;
110145
}
111146

112-
// TODO: add support for modules/subprojects
113-
async function getUriSourceRootsJdt(sourceRoots: string[], _folder: vscode.WorkspaceFolder, uri: string, _hasNblsProjectInfoCommand: boolean, _hasMicronautToolsSubprojectCommand: boolean, api: any) {
147+
// TODO: add support for modules/subprojects?
148+
// NOTE: modules/subprojects are defined by the Micronaut Tools ext., which has NBLS as a required dependency -> getUriSourceRootsNbls will be executed instead of getUriSourceRootsJdt
149+
async function getUriSourceRootsJdt(sourceRoots: string[], _folder: vscode.WorkspaceFolder, uri: string, _hasNblsProjectInfoCommand: boolean, _hasMicronautToolsSubprojectCommand: boolean, api: any): Promise<boolean> {
150+
let foundSourceRoots = false;
114151
try {
115152
const settings = await api.getProjectSettings(uri, [ JDT_SETTINGS_SOURCE_PATHS ]);
116153
if (settings) {
117154
const uriSourceRoots = settings[JDT_SETTINGS_SOURCE_PATHS];
118-
if (uriSourceRoots) {
155+
if (uriSourceRoots?.length) {
156+
foundSourceRoots = true;
119157
for (const uriSourceRoot of uriSourceRoots) {
120158
if (!sourceRoots.includes(uriSourceRoot)) {
121159
sourceRoots.push(uriSourceRoot);
122160
}
123161
}
124162
}
125163
}
164+
return foundSourceRoots;
126165
} catch (err) {
127-
// <project_folder>-parent does not exist
166+
// Error: Given URI does not belong to any Java project.
167+
return false;
128168
}
129169
}
130170

131-
// TODO: integrate with Micronaut Tools to only track packages of the selected GCN application module
132171
export async function getPackages(workspaceFolder?: vscode.WorkspaceFolder): Promise<string[] | undefined> {
172+
logUtils.logInfo(`[projectUtils] Computing packages${workspaceFolder ? ' for folder ' + workspaceFolder.uri.fsPath : ''}...`);
133173
const workspaceFolders = workspaceFolder ? [ workspaceFolder ] : vscode.workspace.workspaceFolders;
134174
if (!workspaceFolders?.length) { // No folder opened
175+
logUtils.logInfo('[projectUtils] No folder opened');
135176
return [];
136177
}
137178

138179
const commands = await vscode.commands.getCommands();
139180
const hasNblsProjectPackagesCommand = commands.includes(NBLS_GET_PACKAGES_COMMAND);
140181
const hasJdtWorkspaceCommand = commands.includes(JDT_EXECUTE_WORKSPACE_COMMAND);
141182
if (!hasNblsProjectPackagesCommand && !hasJdtWorkspaceCommand) {
183+
logUtils.logWarning('[projectUtils] No Java support to compute packages');
142184
// TODO: wait for NBLS/JDT if installed
143185
return undefined; // No Java support available
144186
}
@@ -148,16 +190,45 @@ export async function getPackages(workspaceFolder?: vscode.WorkspaceFolder): Pro
148190

149191
const packages: string[] = [];
150192
const getUriPackages = hasNblsProjectPackagesCommand ? getUriPackagesNbls : getUriPackagesJdt;
193+
logUtils.logInfo(`[projectUtils] Using ${hasNblsProjectPackagesCommand ? 'NBLS' : 'JDT'} to compute packages`);
151194
for (const folder of workspaceFolders) {
152-
await getUriPackages(packages, folder, folder.uri.toString(), hasNblsProjectInfoCommand, hasMicronautToolsSubprojectCommand);
195+
const unrecognizedProjectFolders: vscode.Uri[] = [];
196+
try {
197+
const foundPackages = await getUriPackages(packages, folder, folder.uri.toString(), hasNblsProjectInfoCommand, hasMicronautToolsSubprojectCommand);
198+
if (!foundPackages && !isSupportedProjectUri(folder.uri)) {
199+
unrecognizedProjectFolders.push(folder.uri);
200+
}
201+
} catch (err) {
202+
logUtils.logError(`[projectUtils] Error computing packages: ${err}`);
203+
}
204+
205+
// Try to find a nested supported project folder
206+
while (unrecognizedProjectFolders.length) {
207+
const unrecognizedProjectFolder = unrecognizedProjectFolders.shift();
208+
if (unrecognizedProjectFolder) {
209+
const subfolders = fs.readdirSync(unrecognizedProjectFolder.fsPath);
210+
for (const subfolder of subfolders) {
211+
const subfolderUri = vscode.Uri.joinPath(unrecognizedProjectFolder, subfolder);
212+
if (fs.lstatSync(subfolderUri.fsPath)?.isDirectory()) {
213+
const foundPackages = await getUriPackages(packages, folder, subfolderUri.toString(), hasNblsProjectInfoCommand, hasMicronautToolsSubprojectCommand);
214+
if (!foundPackages && !isSupportedProjectUri(folder.uri)) {
215+
unrecognizedProjectFolders.push(subfolderUri);
216+
}
217+
}
218+
}
219+
}
220+
}
153221
}
222+
logUtils.logInfo(`[projectUtils] Found ${packages.length} package(s)`);
154223
return packages;
155224
}
156225

157-
async function getUriPackagesNbls(packages: string[], folder: vscode.WorkspaceFolder, uri: string, hasNblsProjectInfoCommand: boolean, hasMicronautToolsSubprojectCommand: boolean) {
226+
async function getUriPackagesNbls(packages: string[], folder: vscode.WorkspaceFolder, uri: string, hasNblsProjectInfoCommand: boolean, hasMicronautToolsSubprojectCommand: boolean): Promise<boolean> {
227+
let foundPackages = false;
158228
const uriPackages: string[] | undefined = await vscode.commands.executeCommand(NBLS_GET_PACKAGES_COMMAND, uri, true);
159229
if (uriPackages) {
160230
if (uriPackages.length) { // found project packages
231+
foundPackages = true;
161232
for (const uriPackage of uriPackages) {
162233
const wildcardPackage = uriPackage + '.*';
163234
if (!packages.includes(wildcardPackage)) {
@@ -168,41 +239,68 @@ async function getUriPackagesNbls(packages: string[], folder: vscode.WorkspaceFo
168239
if (hasMicronautToolsSubprojectCommand) { // include only packages of the module selected in Micronaut Tools
169240
const subprojectUri = await vscode.commands.executeCommand(MICRONAUT_TOOLS_SELECTED_SUBPROJECT_COMMAND, folder);
170241
if (subprojectUri instanceof vscode.Uri) { // folder tracked by Micronaut Tools and module selected
171-
await getUriPackagesNbls(packages, folder, subprojectUri.toString(), false, false); // false prevents deep search (OK for GCN, may need to be enabled for other projects)
242+
return await getUriPackagesNbls(packages, folder, subprojectUri.toString(), false, false); // false prevents deep search (OK for GCN, may need to be enabled for other projects)
172243
// TODO: include dependency subprojects (oci -> lib)?
173-
return;
174244
}
175245
}
176246
if (hasNblsProjectInfoCommand) { // include packages from all found modules
177247
const infos: any[] = await vscode.commands.executeCommand(NBLS_PROJECT_INFO_COMMAND, uri, { projectStructure: true });
178248
if (infos?.length && infos[0]) {
179249
for (const subproject of infos[0].subprojects) { // multimodule - most likely GCN
180-
await getUriPackagesNbls(packages, folder, subproject, false, false); // false prevents deep search (OK for GCN, may need to be enabled for other projects)
250+
foundPackages = foundPackages || await getUriPackagesNbls(packages, folder, subproject, false, false); // false prevents deep search (OK for GCN, may need to be enabled for other projects)
181251
}
182252
}
183253
}
184254
}
185255
}
256+
return foundPackages;
186257
}
187258

188-
// TODO: add support for modules/subprojects
189-
async function getUriPackagesJdt(packages: string[], _folder: vscode.WorkspaceFolder, uri: string) {
190-
const projectEntries = await getPackageDataJdt({ kind: 2, projectUri: uri });
191-
for (const projectEntry of projectEntries) {
192-
if (projectEntry.entryKind === 1) { // source root
193-
const packageRoots = await getPackageDataJdt({ kind: 3, projectUri: uri, rootPath: projectEntry.path, isHierarchicalView: false });
194-
for (const packageRoot of packageRoots) {
195-
if (packageRoot.kind === 4) { // package root
196-
const wildcardPackage = packageRoot.name + '.*';
197-
if (!packages.includes(wildcardPackage)) {
198-
packages.push(wildcardPackage);
259+
// TODO: add support for modules/subprojects?
260+
// NOTE: modules/subprojects are defined by the Micronaut Tools ext., which has NBLS as a required dependency -> getUriPackagesNbls will be executed instead of getUriPackagesJdt
261+
async function getUriPackagesJdt(packages: string[], _folder: vscode.WorkspaceFolder, uri: string): Promise<boolean> {
262+
let foundPackages = false;
263+
try {
264+
const projectEntries = await getPackageDataJdt({ kind: 2, projectUri: uri });
265+
for (const projectEntry of projectEntries) {
266+
if (projectEntry.entryKind === 1) { // source root
267+
const packageRoots = await getPackageDataJdt({ kind: 3, projectUri: uri, rootPath: projectEntry.path, isHierarchicalView: false });
268+
for (const packageRoot of packageRoots) {
269+
if (packageRoot.kind === 4) { // package root
270+
foundPackages = true;
271+
const wildcardPackage = packageRoot.name + '.*';
272+
if (!packages.includes(wildcardPackage)) {
273+
packages.push(wildcardPackage);
274+
}
199275
}
200276
}
201277
}
202278
}
279+
return foundPackages;
280+
} catch (err) {
281+
// Error: Did not find container for URI <uri>
282+
return false;
203283
}
204284
}
205285

206286
async function getPackageDataJdt(params: { [key: string]: any }): Promise<any[]> {
207287
return await vscode.commands.executeCommand(JDT_EXECUTE_WORKSPACE_COMMAND, JDT_GET_PACKAGE_DATA, params) || [];
208288
}
289+
290+
// Currently supports recognizing Maven Java project root (pom.xml) and Gradle Java project root (settings.gradle or build.gradle)
291+
// Ideally a NBLS / JDT API should be used to identify a project folder supported by the particular LS
292+
function isSupportedProjectUri(uri: vscode.Uri): boolean {
293+
const mavenPomFile = path.join(uri.fsPath, 'pom.xml');
294+
if (fs.existsSync(mavenPomFile) && fs.lstatSync(mavenPomFile)?.isFile()) {
295+
return true;
296+
}
297+
const gradleSettingsFile = path.join(uri.fsPath, 'settings.gradle');
298+
if (fs.existsSync(gradleSettingsFile) && fs.lstatSync(gradleSettingsFile)?.isFile()) {
299+
return true;
300+
}
301+
const gradleBuildFile = path.join(uri.fsPath, 'build.gradle');
302+
if (fs.existsSync(gradleBuildFile) && fs.lstatSync(gradleBuildFile)?.isFile()) {
303+
return true;
304+
}
305+
return false;
306+
}

0 commit comments

Comments
 (0)