Skip to content

Commit 7a216a5

Browse files
Copilothanniavalera
andcommitted
fix: surface cmake --install output and add permission error hint
When cmake --install fails, the extension now: 1. Streams stdout/stderr to the Output channel via an OutputConsumer (matching the CPackOutputLogger pattern) so users see CMake's actual error messages instead of just an exit code. 2. Detects permission-related errors in the output and shows an actionable hint recommending cmake.installPrefix. The containsPermissionError() utility is in installUtils.ts (no vscode dependency) so it is testable in backend unit tests. Adds 6 new unit tests for permission error detection. Existing CMake: Install behavior is unchanged. Co-authored-by: hanniavalera <90047725+hanniavalera@users.noreply.github.com>
1 parent a05c9c1 commit 7a216a5

File tree

3 files changed

+55
-3
lines changed

3 files changed

+55
-3
lines changed

src/cmakeProject.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ import { DebugTrackerFactory, DebuggerInformation, getDebuggerPipeName } from '@
5454
import { NamedTarget, RichTarget, FolderTarget } from '@cmt/drivers/cmakeDriver';
5555

5656
import { CommandResult, ConfigurationType } from 'vscode-cmake-tools';
57-
import { parseInstallComponentsFromContent } from '@cmt/installUtils';
57+
import { parseInstallComponentsFromContent, containsPermissionError } from '@cmt/installUtils';
5858

5959
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
6060
const localize: nls.LocalizeFunc = nls.loadMessageBundle();
@@ -2672,11 +2672,25 @@ export class CMakeProject {
26722672
},
26732673
async (_progress, cancel) => {
26742674
cancel.onCancellationRequested(() => rollbar.invokeAsync(localize('stop.on.cancellation', 'Stop on cancellation'), () => this.stop()));
2675-
const child = driver.executeCommand(cmake.path, args, undefined, {});
2675+
const consumer: proc.OutputConsumer = {
2676+
output(line: string) {
2677+
buildLogger.info(line);
2678+
},
2679+
error(line: string) {
2680+
buildLogger.error(line);
2681+
}
2682+
};
2683+
const child = driver.executeCommand(cmake.path, args, consumer, {});
26762684
const result = await child.result;
26772685
if (result.retc !== 0) {
26782686
buildLogger.error(localize('install.component.failed', 'Install component failed with exit code {0}', result.retc));
26792687
log.showChannel(true);
2688+
const combinedOutput = [result.stdout, result.stderr].filter(s => s).join('\n');
2689+
if (containsPermissionError(combinedOutput)) {
2690+
void vscode.window.showErrorMessage(
2691+
localize('install.component.permission.error',
2692+
'Install failed due to a permission error. Consider setting "cmake.installPrefix" to a writable directory (e.g. $\{workspaceFolder}/_install).'));
2693+
}
26802694
} else {
26812695
buildLogger.info(localize('install.component.finished', 'Install component finished successfully'));
26822696
}

src/installUtils.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,12 @@ export function parseInstallComponentsFromContent(content: string): string[] {
1616
}
1717
return Array.from(components).sort();
1818
}
19+
20+
/**
21+
* Check whether cmake --install output indicates a permission/access error.
22+
* Used to provide an actionable hint to the user.
23+
*/
24+
export function containsPermissionError(output: string): boolean {
25+
const lower = output.toLowerCase();
26+
return lower.includes('permission') || lower.includes('access is denied') || lower.includes('cannot create directory');
27+
}

test/unit-tests/backend/installComponents.test.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { expect } from 'chai';
2-
import { parseInstallComponentsFromContent } from '@cmt/installUtils';
2+
import { parseInstallComponentsFromContent, containsPermissionError } from '@cmt/installUtils';
33

44
suite('parseInstallComponentsFromContent', () => {
55
test('parses single component', () => {
@@ -121,3 +121,32 @@ endif()
121121
expect(components).to.deep.equal(['my-component_v2']);
122122
});
123123
});
124+
125+
suite('containsPermissionError', () => {
126+
test('detects "permission" keyword', () => {
127+
expect(containsPermissionError('file cannot copy: Permission denied')).to.be.true;
128+
});
129+
130+
test('detects "cannot create directory" from CMake', () => {
131+
expect(containsPermissionError(
132+
'file cannot create directory: C:/Program Files/MyProject/include. Maybe need administrative privileges.'
133+
)).to.be.true;
134+
});
135+
136+
test('detects "Access is denied" (Windows)', () => {
137+
expect(containsPermissionError('Error: Access is denied.')).to.be.true;
138+
});
139+
140+
test('is case-insensitive', () => {
141+
expect(containsPermissionError('PERMISSION DENIED')).to.be.true;
142+
expect(containsPermissionError('Cannot Create Directory: /opt/app')).to.be.true;
143+
});
144+
145+
test('returns false for unrelated errors', () => {
146+
expect(containsPermissionError('CMake Error: install(TARGETS) given no ARCHIVE DESTINATION')).to.be.false;
147+
});
148+
149+
test('returns false for empty string', () => {
150+
expect(containsPermissionError('')).to.be.false;
151+
});
152+
});

0 commit comments

Comments
 (0)