Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion src/managers/builtin/pipUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,12 +290,34 @@ export async function getProjectInstallable(
const uniqueResults = Array.from(new Map(results.map((uri) => [uri.fsPath, uri])).values());

const fsPaths = projects.map((p) => p.uri.fsPath);
// Compute depth relative to the owning project root so ordering reflects
// "shallower within the project", independent of where the project lives on disk.
const depthFromProject = (uri: Uri): number => {
const projectRoot = api.getPythonProject(uri)?.uri.fsPath;
if (!projectRoot) {
return Number.MAX_SAFE_INTEGER;
}
const rel = path.relative(projectRoot, uri.fsPath);
if (!rel) {
return 0;
}
return rel.split(path.sep).filter((segment) => segment.length > 0).length;
};
const filtered = uniqueResults
.filter((uri) => {
const p = api.getPythonProject(uri)?.uri.fsPath;
return p && fsPaths.includes(p);
})
.sort();
.sort((a, b) => {
// Sort by path depth relative to the project root (shallowest first) so
// top-level files like requirements.txt appear before deeply nested ones.
const depthA = depthFromProject(a);
const depthB = depthFromProject(b);
if (depthA !== depthB) {
return depthA - depthB;
}
return a.fsPath.localeCompare(b.fsPath);
});
Comment thread
eleanorjboyd marked this conversation as resolved.

await Promise.all(
filtered.map(async (uri) => {
Expand Down
35 changes: 35 additions & 0 deletions src/test/managers/builtin/pipUtils.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,4 +199,39 @@ suite('Pip Utils - getProjectInstallable', () => {
assert.ok(firstResult.uri, 'Should have a URI');
assert.ok(firstResult.uri.fsPath.startsWith(workspacePath), 'Should be in workspace directory');
});

test('should sort shallower files before deeper ones', async () => {
// Arrange: Use the shared workspacePath from setup() so paths are platform-safe.
const workspacePath = Uri.file('/test/path/root').fsPath;
const rootReqPath = path.join(workspacePath, 'requirements.txt');
const subdirReqPath = path.join(workspacePath, 'subdir', 'dev-requirements.txt');
const deepReqPath = path.join(workspacePath, 'deep', 'nested', 'sub', 'requirements.txt');

// Return files at different depths, with deeper ones discovered first.
findFilesStub.callsFake((pattern: string) => {
if (pattern === '**/*requirements*.txt') {
return Promise.resolve([Uri.file(deepReqPath), Uri.file(subdirReqPath)]);
} else if (pattern === '*requirements*.txt') {
return Promise.resolve([Uri.file(rootReqPath)]);
} else if (pattern === '**/requirements/*.txt') {
return Promise.resolve([]);
} else if (pattern === '**/pyproject.toml') {
return Promise.resolve([]);
}
return Promise.resolve([]);
});

// Act
const projects = [{ name: 'workspace', uri: Uri.file(workspacePath) }];
const result = (await getProjectInstallable(mockApi as PythonEnvironmentApi, projects)).installables;

// Assert: order by fsPath so the two `requirements.txt` files are unambiguous.
assert.strictEqual(result.length, 3);
const fsPaths = result.map((r) => r.uri!.fsPath);
assert.deepStrictEqual(
fsPaths,
[rootReqPath, subdirReqPath, deepReqPath],
'Files should be ordered by depth relative to the project root',
);
});
Comment thread
eleanorjboyd marked this conversation as resolved.
});
Loading