Skip to content

Commit 6140ae5

Browse files
authored
feat: handle when starting debug session failed (#1809)
If the sketch has not been verified, IDE2 offers the user a verify action. Closes #808 Signed-off-by: Akos Kitta <[email protected]>
1 parent afb02da commit 6140ae5

File tree

7 files changed

+126
-16
lines changed

7 files changed

+126
-16
lines changed

Diff for: arduino-ide-extension/src/browser/contributions/debug.ts

+47-8
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@ import { Event, Emitter } from '@theia/core/lib/common/event';
33
import { HostedPluginSupport } from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin';
44
import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
55
import { NotificationCenter } from '../notification-center';
6-
import { Board, BoardsService, ExecutableService } from '../../common/protocol';
6+
import {
7+
Board,
8+
BoardsService,
9+
ExecutableService,
10+
Sketch,
11+
} from '../../common/protocol';
712
import { BoardsServiceProvider } from '../boards/boards-service-provider';
813
import {
914
URI,
@@ -16,9 +21,8 @@ import { MaybePromise, MenuModelRegistry, nls } from '@theia/core/lib/common';
1621
import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl';
1722
import { ArduinoMenus } from '../menu/arduino-menus';
1823

19-
import { MainMenuManager } from '../../common/main-menu-manager';
20-
2124
const COMPILE_FOR_DEBUG_KEY = 'arduino-compile-for-debug';
25+
2226
@injectable()
2327
export class Debug extends SketchContribution {
2428
@inject(HostedPluginSupport)
@@ -36,9 +40,6 @@ export class Debug extends SketchContribution {
3640
@inject(BoardsServiceProvider)
3741
private readonly boardsServiceProvider: BoardsServiceProvider;
3842

39-
@inject(MainMenuManager)
40-
private readonly mainMenuManager: MainMenuManager;
41-
4243
/**
4344
* If `undefined`, debugging is enabled. Otherwise, the reason why it's disabled.
4445
*/
@@ -203,7 +204,28 @@ export class Debug extends SketchContribution {
203204
sketchPath,
204205
configPath,
205206
};
206-
return this.commandService.executeCommand('arduino.debug.start', config);
207+
try {
208+
await this.commandService.executeCommand('arduino.debug.start', config);
209+
} catch (err) {
210+
if (await this.isSketchNotVerifiedError(err, sketch)) {
211+
const yes = nls.localize('vscode/extensionsUtils/yes', 'Yes');
212+
const answer = await this.messageService.error(
213+
nls.localize(
214+
'arduino/debug/sketchIsNotCompiled',
215+
"Sketch '{0}' must be verified before starting a debug session. Please verify the sketch and start debugging again. Do you want to verify the sketch now?",
216+
sketch.name
217+
),
218+
yes
219+
);
220+
if (answer === yes) {
221+
this.commandService.executeCommand('arduino-verify-sketch');
222+
}
223+
} else {
224+
this.messageService.error(
225+
err instanceof Error ? err.message : String(err)
226+
);
227+
}
228+
}
207229
}
208230

209231
get compileForDebug(): boolean {
@@ -215,7 +237,24 @@ export class Debug extends SketchContribution {
215237
const oldState = this.compileForDebug;
216238
const newState = !oldState;
217239
window.localStorage.setItem(COMPILE_FOR_DEBUG_KEY, String(newState));
218-
this.mainMenuManager.update();
240+
this.menuManager.update();
241+
}
242+
243+
private async isSketchNotVerifiedError(
244+
err: unknown,
245+
sketch: Sketch
246+
): Promise<boolean> {
247+
if (err instanceof Error) {
248+
try {
249+
const tempBuildPaths = await this.sketchService.tempBuildPath(sketch);
250+
return tempBuildPaths.some((tempBuildPath) =>
251+
err.message.includes(tempBuildPath)
252+
);
253+
} catch {
254+
return false;
255+
}
256+
}
257+
return false;
219258
}
220259
}
221260
export namespace Debug {

Diff for: arduino-ide-extension/src/common/protocol/sketches-service.ts

+11
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,17 @@ export interface SketchesService {
105105
* Recursively deletes the sketch folder with all its content.
106106
*/
107107
deleteSketch(sketch: Sketch): Promise<void>;
108+
109+
/**
110+
* This is the JS/TS re-implementation of [`GenBuildPath`](https://github.com/arduino/arduino-cli/blob/c0d4e4407d80aabad81142693513b3306759cfa6/arduino/sketch/sketch.go#L296-L306) of the CLI.
111+
* Pass in a sketch and get the build temporary folder filesystem path calculated from the main sketch file location. Can be multiple ones. This method does not check the existence of the sketch.
112+
*
113+
* The case sensitivity of the drive letter on Windows matters when the CLI calculates the MD5 hash of the temporary build folder.
114+
* IDE2 does not know and does not want to rely on how the CLI treats the paths: with lowercase or uppercase drive letters.
115+
* Hence, IDE2 has to provide multiple build paths on Windows. This hack will be obsolete when the CLI can provide error codes:
116+
* https://github.com/arduino/arduino-cli/issues/1762.
117+
*/
118+
tempBuildPath(sketch: Sketch): Promise<string[]>;
108119
}
109120

110121
export interface SketchRef {

Diff for: arduino-ide-extension/src/common/utils.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ export function firstToUpperCase(what: string): string {
1313
return what.charAt(0).toUpperCase() + what.slice(1);
1414
}
1515

16-
export function isNullOrUndefined(what: any): what is undefined | null {
16+
export function startsWithUpperCase(what: string): boolean {
17+
return !!what && what.charAt(0) === firstToUpperCase(what.charAt(0));
18+
}
19+
20+
export function isNullOrUndefined(what: unknown): what is undefined | null {
1721
return what === undefined || what === null;
1822
}

Diff for: arduino-ide-extension/src/node/is-temp-sketch.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@ import { isWindows, isOSX } from '@theia/core/lib/common/os';
44
import { injectable } from '@theia/core/shared/inversify';
55
import { firstToLowerCase } from '../common/utils';
66

7-
const Win32DriveRegex = /^[a-zA-Z]:\\/;
7+
export const Win32DriveRegex = /^[a-zA-Z]:\\/;
88
export const TempSketchPrefix = '.arduinoIDE-unsaved';
99

1010
@injectable()
1111
export class IsTempSketch {
1212
// If on macOS, the `temp-dir` lib will make sure there is resolved realpath.
13-
// If on Windows, the `C:\Users\KITTAA~1\AppData\Local\Temp` path will be resolved and normalized to `C:\Users\kittaakos\AppData\Local\Temp`.
13+
// If on Windows, the `C:\Users\KITTAA~1\AppData\Local\Temp` path will be resolved and normalized to `c:\Users\kittaakos\AppData\Local\Temp`.
1414
// Note: VS Code URI normalizes the drive letter. `C:` will be converted into `c:`.
1515
// https://github.com/Microsoft/vscode/issues/68325#issuecomment-462239992
16-
private readonly tempDirRealpath = isOSX
16+
readonly tempDirRealpath = isOSX
1717
? tempDir
1818
: maybeNormalizeDrive(fs.realpathSync.native(tempDir));
1919

Diff for: arduino-ide-extension/src/node/sketches-service-impl.ts

+57-2
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,16 @@ import {
3333
IsTempSketch,
3434
maybeNormalizeDrive,
3535
TempSketchPrefix,
36+
Win32DriveRegex,
3637
} from './is-temp-sketch';
3738
import { join } from 'path';
3839
import { ErrnoException } from './utils/errors';
40+
import { isWindows } from '@theia/core/lib/common/os';
41+
import {
42+
firstToLowerCase,
43+
firstToUpperCase,
44+
startsWithUpperCase,
45+
} from '../common/utils';
3946

4047
const RecentSketches = 'recent-sketches.json';
4148
const DefaultIno = `void setup() {
@@ -566,11 +573,59 @@ export class SketchesServiceImpl
566573
return FileUri.create(genBuildPath).toString();
567574
}
568575

569-
async getIdeTempFolderPath(sketch: Sketch): Promise<string> {
576+
private async getIdeTempFolderPath(sketch: Sketch): Promise<string> {
570577
const sketchPath = FileUri.fsPath(sketch.uri);
571578
await fs.readdir(sketchPath); // Validates the sketch folder and rejects if not accessible.
572579
const suffix = crypto.createHash('md5').update(sketchPath).digest('hex');
573-
return path.join(os.tmpdir(), `arduino-ide2-${suffix}`);
580+
return path.join(
581+
this.isTempSketch.tempDirRealpath,
582+
`arduino-ide2-${suffix}`
583+
);
584+
}
585+
586+
async tempBuildPath(sketch: Sketch): Promise<string[]> {
587+
const sketchPath = FileUri.fsPath(sketch.uri);
588+
const { tempDirRealpath } = this.isTempSketch;
589+
const tempBuildPaths = [
590+
this.tempBuildPathMD5Hash(tempDirRealpath, sketchPath),
591+
];
592+
593+
// If on Windows, provide both the upper and the lowercase drive letter MD5 hashes. All together four paths are expected:
594+
// One of them should match if the sketch is not yet compiled.
595+
// https://github.com/arduino/arduino-ide/pull/1809#discussion_r1071031040
596+
if (isWindows && Win32DriveRegex.test(tempDirRealpath)) {
597+
const toggleFirstCharCasing = (s: string) =>
598+
startsWithUpperCase(s) ? firstToLowerCase(s) : firstToUpperCase(s);
599+
const otherCaseTempDirRealPath = toggleFirstCharCasing(tempDirRealpath);
600+
tempBuildPaths.push(
601+
this.tempBuildPathMD5Hash(otherCaseTempDirRealPath, sketchPath)
602+
);
603+
if (Win32DriveRegex.test(sketchPath)) {
604+
const otherCaseSketchPath = toggleFirstCharCasing(sketchPath);
605+
tempBuildPaths.push(
606+
this.tempBuildPathMD5Hash(tempDirRealpath, otherCaseSketchPath),
607+
this.tempBuildPathMD5Hash(
608+
otherCaseTempDirRealPath,
609+
otherCaseSketchPath
610+
)
611+
);
612+
}
613+
}
614+
return tempBuildPaths;
615+
}
616+
617+
private tempBuildPathMD5Hash(tempFolderPath: string, path: string): string {
618+
return join(tempFolderPath, this.tempBuildFolderMD5Hash(path));
619+
}
620+
621+
private tempBuildFolderMD5Hash(path: string): string {
622+
const hash = crypto
623+
.createHash('md5')
624+
.update(path)
625+
.digest('hex')
626+
.toUpperCase();
627+
const folderName = `arduino-sketch-${hash}`;
628+
return folderName;
574629
}
575630

576631
async deleteSketch(sketch: Sketch): Promise<void> {

Diff for: i18n/en.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,8 @@
191191
"debugWithMessage": "Debug - {0}",
192192
"debuggingNotSupported": "Debugging is not supported by '{0}'",
193193
"noPlatformInstalledFor": "Platform is not installed for '{0}'",
194-
"optimizeForDebugging": "Optimize for Debugging"
194+
"optimizeForDebugging": "Optimize for Debugging",
195+
"sketchIsNotCompiled": "Sketch '{0}' must be verified before starting a debug session. Please verify the sketch and start debugging again. Do you want to verify the sketch now?"
195196
},
196197
"dialog": {
197198
"dontAskAgain": "Don't ask again"

Diff for: package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@
7575
"theiaPluginsDir": "plugins",
7676
"theiaPlugins": {
7777
"vscode-builtin-cpp": "https://open-vsx.org/api/vscode/cpp/1.52.1/file/vscode.cpp-1.52.1.vsix",
78-
"vscode-arduino-tools": "https://downloads.arduino.cc/vscode-arduino-tools/vscode-arduino-tools-0.0.2-beta.5.vsix",
78+
"vscode-arduino-tools": "https://downloads.arduino.cc/vscode-arduino-tools/vscode-arduino-tools-0.0.2-beta.7.vsix",
7979
"vscode-builtin-json": "https://open-vsx.org/api/vscode/json/1.46.1/file/vscode.json-1.46.1.vsix",
8080
"vscode-builtin-json-language-features": "https://open-vsx.org/api/vscode/json-language-features/1.46.1/file/vscode.json-language-features-1.46.1.vsix",
8181
"cortex-debug": "https://downloads.arduino.cc/marus25.cortex-debug/marus25.cortex-debug-1.5.1.vsix",

0 commit comments

Comments
 (0)