Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 007edc7

Browse files
author
Akos Kitta
committedNov 13, 2023
feat: handle multiple configs per sketch
1 parent 32ba04a commit 007edc7

File tree

4 files changed

+108
-24
lines changed

4 files changed

+108
-24
lines changed
 

‎src/debug.ts

Lines changed: 69 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import path from 'node:path';
1010
import setValue from 'set-value';
1111
import unsetValue from 'unset-value';
1212
import vscode from 'vscode';
13+
import { isENOENT } from './errno';
1314
import { ExecOptions, exec } from './exec';
1415
import type { BoardIdentifier } from './typings';
1516
import type { CortexDebugLaunchAttributes } from './typings/cortexDebug';
@@ -21,7 +22,7 @@ export function activateDebug(_: vscode.ExtensionContext): vscode.Disposable {
2122
'arduino.debug.start',
2223
async (params: StartDebugParams) => {
2324
const launchConfig = await createLaunchConfig(params);
24-
return startDebug(params.launchConfigPath, launchConfig);
25+
return startDebug(params.launchConfigDirPath, launchConfig);
2526
}
2627
),
2728
vscode.commands.registerCommand(
@@ -46,10 +47,11 @@ export interface StartDebugParams {
4647
*/
4748
readonly sketchPath: string;
4849
/**
49-
* Location where the `launch.json` will be created on the fly before starting every debug session.
50-
* If not defined, it falls back to `sketchPath/.vscode/launch.json`.
50+
* Absolute filesystem path of the directory where the `launch.json` will be updated before starting every debug session.
51+
* If the launch config file is absent, it will be created.
52+
* If not defined, it falls back to `sketchPath/.vscode/launch.json` and uses VS Code APIs to alter the config.
5153
*/
52-
readonly launchConfigPath?: string;
54+
readonly launchConfigDirPath?: string;
5355
/**
5456
* Absolute path to the `arduino-cli.yaml` file. If not specified, it falls back to `~/.arduinoIDE/arduino-cli.yaml`.
5557
*/
@@ -115,10 +117,10 @@ function isCustomDebugConfig(arg: unknown): arg is CustomDebugConfig {
115117
const cortexDebug = 'cortex-debug';
116118

117119
async function startDebug(
118-
launchConfigPath: string | undefined,
120+
launchConfigDirPath: string | undefined,
119121
launchConfig: ArduinoDebugLaunchConfig
120122
): Promise<StartDebugResult> {
121-
await updateLaunchConfigs(launchConfigPath, launchConfig);
123+
await updateLaunchConfigs(launchConfigDirPath, launchConfig);
122124
return vscode.debug.startDebugging(undefined, launchConfig);
123125
}
124126

@@ -188,7 +190,7 @@ async function loadDebugCustomJson(
188190
);
189191
return parseCustomDebugConfigs(raw);
190192
} catch (err) {
191-
if (err instanceof Error && 'code' in err && err.code === 'ENOENT') {
193+
if (isENOENT(err)) {
192194
return [];
193195
}
194196
throw err;
@@ -218,7 +220,7 @@ function parseJson(raw: string): any | undefined {
218220
}
219221

220222
function buildDebugInfoArgs(
221-
params: Omit<StartDebugParams, 'launchConfigPath'>
223+
params: Omit<StartDebugParams, 'launchConfigDirPath'>
222224
): Readonly<{
223225
file: string;
224226
args: readonly string[];
@@ -299,33 +301,78 @@ function replaceValue(
299301
}
300302

301303
// Iteration plan:
302-
// 1. update json configs object with a single config entry and write the file with fs (IDE 2.2.1 behavior)
303-
// 2. update json configs object by merging in the current config entry, and write file with fs
304+
// 1. (done) update json configs object with a single config entry and write the file with fs (IDE 2.2.1 behavior)
305+
// 2. (done) update json configs object by merging in the current config entry, and write file with fs
304306
// 3. same as (2) but use jsonc to nicely update the JSON file
305307
// 4. use the getConfiguration('launch') API to update the config. It must be verified whether it works in Theia
306308
async function updateLaunchConfigs(
307-
launchConfigPath: string | undefined,
309+
launchConfigDirPath: string | undefined,
308310
launchConfig: ArduinoDebugLaunchConfig
309311
): Promise<void> {
310-
const launchConfigs = {
311-
version: '0.2.0',
312-
configurations: [
313-
{
314-
...launchConfig,
315-
},
316-
],
317-
};
318-
if (launchConfigPath) {
319-
await fs.mkdir(launchConfigPath, { recursive: true });
312+
const launchConfigs = await (launchConfigDirPath
313+
? loadLaunchConfigsFile(launchConfigDirPath)
314+
: vscode.workspace.getConfiguration().get<LaunchConfigs>('launch') ??
315+
createEmptyLaunchConfigs());
316+
317+
const index = launchConfigs.configurations.findIndex(
318+
(c) => c['configId'] === launchConfig.configId
319+
);
320+
if (index < 0) {
321+
launchConfigs.configurations.push(launchConfig);
322+
} else {
323+
launchConfigs.configurations.splice(index, 1, launchConfig);
324+
}
325+
326+
if (launchConfigDirPath) {
327+
await fs.mkdir(launchConfigDirPath, { recursive: true });
320328
await fs.writeFile(
321-
path.join(launchConfigPath, 'launch.json'),
329+
path.join(launchConfigDirPath, 'launch.json'),
322330
JSON.stringify(launchConfigs, null, 2)
323331
);
324332
} else {
325333
const configuration = vscode.workspace.getConfiguration();
326334
await configuration.update('launch', launchConfigs, false);
327335
}
328336
}
337+
type LaunchConfigs = {
338+
version: '0.2.0';
339+
configurations: vscode.DebugConfiguration[];
340+
};
341+
function createEmptyLaunchConfigs(): LaunchConfigs {
342+
return {
343+
version: '0.2.0',
344+
configurations: [],
345+
};
346+
}
347+
function isLaunchConfigs(arg: unknown): arg is LaunchConfigs {
348+
return (
349+
typeof arg === 'object' &&
350+
arg !== null &&
351+
(<LaunchConfigs>arg).version === '0.2.0' &&
352+
Array.isArray((<LaunchConfigs>arg).configurations)
353+
);
354+
}
355+
356+
async function loadLaunchConfigsFile(
357+
launchConfigDirPath: string
358+
): Promise<LaunchConfigs> {
359+
try {
360+
const raw = await fs.readFile(
361+
path.join(launchConfigDirPath, 'launch.json'),
362+
{ encoding: 'utf8' }
363+
);
364+
const maybeConfigs = parseJson(raw);
365+
if (isLaunchConfigs(maybeConfigs)) {
366+
return maybeConfigs;
367+
}
368+
return createEmptyLaunchConfigs();
369+
} catch (err) {
370+
if (isENOENT(err)) {
371+
return createEmptyLaunchConfigs();
372+
}
373+
throw err;
374+
}
375+
}
329376

330377
async function cliExec<T = Record<string, unknown>>(
331378
cliPath: string,

‎src/errno.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function isENOENT(arg: unknown): arg is Error & { code: 'ENOENT' } {
2+
return arg instanceof Error && 'code' in arg && arg.code === 'ENOENT';
3+
}

‎src/test/suite/debug.test.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,39 @@ describe('debug', () => {
519519
});
520520

521521
it('should update the launch config if present', async () => {
522+
const tempDir = await tracked.mkdir();
523+
const existing = {
524+
version: '0.2.0',
525+
configurations: [launchConfig],
526+
};
527+
await fs.writeFile(
528+
path.join(tempDir, 'launch.json'),
529+
JSON.stringify(existing)
530+
);
531+
const actualExisting = JSON.parse(
532+
await fs.readFile(path.join(tempDir, 'launch.json'), {
533+
encoding: 'utf8',
534+
})
535+
);
536+
assert.deepStrictEqual(actualExisting, existing);
537+
538+
const modifiedLaunchConfig = {
539+
...launchConfig,
540+
executable: 'C:\\path\\to\\another\\exe',
541+
};
542+
await updateLaunchConfigs(tempDir, modifiedLaunchConfig);
543+
const actual = JSON.parse(
544+
await fs.readFile(path.join(tempDir, 'launch.json'), {
545+
encoding: 'utf8',
546+
})
547+
);
548+
assert.deepStrictEqual(actual, {
549+
version: '0.2.0',
550+
configurations: [modifiedLaunchConfig],
551+
});
552+
});
553+
554+
it('should insert a new launch config', async () => {
522555
const tempDir = await tracked.mkdir();
523556
const existing = {
524557
version: '0.2.0',
@@ -543,7 +576,7 @@ describe('debug', () => {
543576
);
544577
assert.deepStrictEqual(actual, {
545578
version: '0.2.0',
546-
configurations: [launchConfig],
579+
configurations: [otherLaunchConfig, launchConfig],
547580
});
548581
});
549582
});

‎src/test/testEnv.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import fs from 'node:fs/promises';
22
import path from 'node:path';
33
import type temp from 'temp';
44
import { __tests__ } from '../debug';
5+
import { isENOENT } from '../errno';
56
import { exec } from '../exec';
67

78
export interface TestEnv {
@@ -42,7 +43,7 @@ async function prepareWithGit(params: PrepareTestEnvParams): Promise<void> {
4243
}
4344
throw new Error(`${arduinoGitPath} is not a directory.`);
4445
} catch (err) {
45-
if (err instanceof Error && 'code' in err && err.code === 'ENOENT') {
46+
if (isENOENT(err)) {
4647
// continue
4748
} else {
4849
throw err;

0 commit comments

Comments
 (0)
Please sign in to comment.