Skip to content

Commit de47e58

Browse files
authored
Enhance sorting of installable files to prioritize shallower paths in getProjectInstallable function (#1428)
helps with #1429
1 parent 7c06c11 commit de47e58

2 files changed

Lines changed: 58 additions & 1 deletion

File tree

src/managers/builtin/pipUtils.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,12 +290,34 @@ export async function getProjectInstallable(
290290
const uniqueResults = Array.from(new Map(results.map((uri) => [uri.fsPath, uri])).values());
291291

292292
const fsPaths = projects.map((p) => p.uri.fsPath);
293+
// Compute depth relative to the owning project root so ordering reflects
294+
// "shallower within the project", independent of where the project lives on disk.
295+
const depthFromProject = (uri: Uri): number => {
296+
const projectRoot = api.getPythonProject(uri)?.uri.fsPath;
297+
if (!projectRoot) {
298+
return Number.MAX_SAFE_INTEGER;
299+
}
300+
const rel = path.relative(projectRoot, uri.fsPath);
301+
if (!rel) {
302+
return 0;
303+
}
304+
return rel.split(path.sep).filter((segment) => segment.length > 0).length;
305+
};
293306
const filtered = uniqueResults
294307
.filter((uri) => {
295308
const p = api.getPythonProject(uri)?.uri.fsPath;
296309
return p && fsPaths.includes(p);
297310
})
298-
.sort();
311+
.sort((a, b) => {
312+
// Sort by path depth relative to the project root (shallowest first) so
313+
// top-level files like requirements.txt appear before deeply nested ones.
314+
const depthA = depthFromProject(a);
315+
const depthB = depthFromProject(b);
316+
if (depthA !== depthB) {
317+
return depthA - depthB;
318+
}
319+
return a.fsPath.localeCompare(b.fsPath);
320+
});
299321

300322
await Promise.all(
301323
filtered.map(async (uri) => {

src/test/managers/builtin/pipUtils.unit.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,4 +199,39 @@ suite('Pip Utils - getProjectInstallable', () => {
199199
assert.ok(firstResult.uri, 'Should have a URI');
200200
assert.ok(firstResult.uri.fsPath.startsWith(workspacePath), 'Should be in workspace directory');
201201
});
202+
203+
test('should sort shallower files before deeper ones', async () => {
204+
// Arrange: Use the shared workspacePath from setup() so paths are platform-safe.
205+
const workspacePath = Uri.file('/test/path/root').fsPath;
206+
const rootReqPath = path.join(workspacePath, 'requirements.txt');
207+
const subdirReqPath = path.join(workspacePath, 'subdir', 'dev-requirements.txt');
208+
const deepReqPath = path.join(workspacePath, 'deep', 'nested', 'sub', 'requirements.txt');
209+
210+
// Return files at different depths, with deeper ones discovered first.
211+
findFilesStub.callsFake((pattern: string) => {
212+
if (pattern === '**/*requirements*.txt') {
213+
return Promise.resolve([Uri.file(deepReqPath), Uri.file(subdirReqPath)]);
214+
} else if (pattern === '*requirements*.txt') {
215+
return Promise.resolve([Uri.file(rootReqPath)]);
216+
} else if (pattern === '**/requirements/*.txt') {
217+
return Promise.resolve([]);
218+
} else if (pattern === '**/pyproject.toml') {
219+
return Promise.resolve([]);
220+
}
221+
return Promise.resolve([]);
222+
});
223+
224+
// Act
225+
const projects = [{ name: 'workspace', uri: Uri.file(workspacePath) }];
226+
const result = (await getProjectInstallable(mockApi as PythonEnvironmentApi, projects)).installables;
227+
228+
// Assert: order by fsPath so the two `requirements.txt` files are unambiguous.
229+
assert.strictEqual(result.length, 3);
230+
const fsPaths = result.map((r) => r.uri!.fsPath);
231+
assert.deepStrictEqual(
232+
fsPaths,
233+
[rootReqPath, subdirReqPath, deepReqPath],
234+
'Files should be ordered by depth relative to the project root',
235+
);
236+
});
202237
});

0 commit comments

Comments
 (0)