Skip to content

Commit d648159

Browse files
Akos Kittakittaakos
Akos Kitta
authored andcommitted
ATL-972: Moved the './theia/launch.json' config into a temp folder.
Signed-off-by: Akos Kitta <[email protected]>
1 parent acbd98d commit d648159

File tree

6 files changed

+193
-25
lines changed

6 files changed

+193
-25
lines changed

Diff for: arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts

+5
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@ import { OutputToolbarContribution } from './theia/output/output-toolbar-contrib
145145
import { AddZipLibrary } from './contributions/add-zip-library';
146146
import { WorkspaceVariableContribution as TheiaWorkspaceVariableContribution } from '@theia/workspace/lib/browser/workspace-variable-contribution';
147147
import { WorkspaceVariableContribution } from './theia/workspace/workspace-variable-contribution';
148+
import { DebugConfigurationManager } from './theia/debug/debug-configuration-manager';
149+
import { DebugConfigurationManager as TheiaDebugConfigurationManager } from '@theia/debug/lib/browser/debug-configuration-manager';
148150

149151
const ElementQueries = require('css-element-queries/src/ElementQueries');
150152

@@ -394,6 +396,9 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
394396
// To remove the `Run` menu item from the application menu.
395397
bind(DebugFrontendApplicationContribution).toSelf().inSingletonScope();
396398
rebind(TheiaDebugFrontendApplicationContribution).toService(DebugFrontendApplicationContribution);
399+
// To be able to use a `launch.json` from outside of the workspace.
400+
bind(DebugConfigurationManager).toSelf().inSingletonScope();
401+
rebind(TheiaDebugConfigurationManager).toService(DebugConfigurationManager);
397402

398403
// Preferences
399404
bindArduinoPreferences(bind);

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

+6-3
Original file line numberDiff line numberDiff line change
@@ -106,17 +106,20 @@ export class Debug extends SketchContribution {
106106
if (!sketch) {
107107
return;
108108
}
109-
const [cliPath, sketchPath] = await Promise.all([
109+
const ideTempFolderUri = await this.sketchService.getIdeTempFolderUri(sketch);
110+
const [cliPath, sketchPath, configPath] = await Promise.all([
110111
this.fileService.fsPath(new URI(executables.cliUri)),
111-
this.fileService.fsPath(new URI(sketch.uri))
112+
this.fileService.fsPath(new URI(sketch.uri)),
113+
this.fileService.fsPath(new URI(ideTempFolderUri)),
112114
])
113115
const config = {
114116
cliPath,
115117
board: {
116118
fqbn,
117119
name
118120
},
119-
sketchPath
121+
sketchPath,
122+
configPath
120123
};
121124
return this.commandService.executeCommand('arduino.debug.start', config);
122125
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import debounce = require('p-debounce');
2+
import { inject, injectable, postConstruct } from 'inversify';
3+
import URI from '@theia/core/lib/common/uri';
4+
import { Event, Emitter } from '@theia/core/lib/common/event';
5+
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
6+
import { DebugConfiguration } from '@theia/debug/lib/common/debug-common';
7+
import { DebugConfigurationModel as TheiaDebugConfigurationModel } from '@theia/debug/lib/browser/debug-configuration-model';
8+
import { DebugConfigurationManager as TheiaDebugConfigurationManager } from '@theia/debug/lib/browser/debug-configuration-manager';
9+
import { SketchesService } from '../../../common/protocol';
10+
import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl';
11+
import { DebugConfigurationModel } from './debug-configuration-model';
12+
import { FileOperationError, FileOperationResult } from '@theia/filesystem/lib/common/files';
13+
14+
@injectable()
15+
export class DebugConfigurationManager extends TheiaDebugConfigurationManager {
16+
17+
@inject(SketchesService)
18+
protected readonly sketchesService: SketchesService;
19+
20+
@inject(SketchesServiceClientImpl)
21+
protected readonly sketchesServiceClient: SketchesServiceClientImpl;
22+
23+
@inject(FrontendApplicationStateService)
24+
protected readonly appStateService: FrontendApplicationStateService;
25+
26+
protected onTempContentDidChangeEmitter = new Emitter<TheiaDebugConfigurationModel.JsonContent>();
27+
get onTempContentDidChange(): Event<TheiaDebugConfigurationModel.JsonContent> {
28+
return this.onTempContentDidChangeEmitter.event;
29+
}
30+
31+
@postConstruct()
32+
protected async init(): Promise<void> {
33+
super.init();
34+
this.appStateService.reachedState('ready').then(async () => {
35+
const tempContent = await this.getTempLaunchJsonContent();
36+
if (!tempContent) {
37+
// No active sketch.
38+
return;
39+
}
40+
// Watch the file of the container folder.
41+
this.fileService.watch(tempContent instanceof URI ? tempContent : tempContent.uri);
42+
// Use the normalized temp folder name. We cannot compare Theia URIs here.
43+
// /var/folders/k3/d2fkvv1j16v3_rz93k7f74180000gn/T/arduino-ide2-a0337d47f86b24a51df3dbcf2cc17925/launch.json
44+
// /private/var/folders/k3/d2fkvv1j16v3_rz93k7f74180000gn/T/arduino-ide2-A0337D47F86B24A51DF3DBCF2CC17925/launch.json
45+
const tempFolderName = (tempContent instanceof URI ? tempContent : tempContent.uri.parent).path.base.toLowerCase();
46+
this.fileService.onDidFilesChange(event => {
47+
for (const { resource } of event.changes) {
48+
if (resource.path.base === 'launch.json' && resource.parent.path.base.toLowerCase() === tempFolderName) {
49+
this.getTempLaunchJsonContent().then(config => {
50+
if (config && !(config instanceof URI)) {
51+
this.onTempContentDidChangeEmitter.fire(config);
52+
}
53+
});
54+
break;
55+
}
56+
}
57+
});
58+
this.updateModels();
59+
});
60+
}
61+
62+
protected updateModels = debounce(async () => {
63+
await this.appStateService.reachedState('ready');
64+
const roots = await this.workspaceService.roots;
65+
const toDelete = new Set(this.models.keys());
66+
for (const rootStat of roots) {
67+
const key = rootStat.resource.toString();
68+
toDelete.delete(key);
69+
if (!this.models.has(key)) {
70+
const tempContent = await this.getTempLaunchJsonContent();
71+
if (!tempContent) {
72+
continue;
73+
}
74+
const configurations: DebugConfiguration[] = tempContent instanceof URI ? [] : tempContent.configurations;
75+
const uri = tempContent instanceof URI ? undefined : tempContent.uri;
76+
const model = new DebugConfigurationModel(key, this.preferences, configurations, uri, this.onTempContentDidChange);
77+
model.onDidChange(() => this.updateCurrent());
78+
model.onDispose(() => this.models.delete(key));
79+
this.models.set(key, model);
80+
}
81+
}
82+
for (const uri of toDelete) {
83+
const model = this.models.get(uri);
84+
if (model) {
85+
model.dispose();
86+
}
87+
}
88+
this.updateCurrent();
89+
}, 500);
90+
91+
protected async getTempLaunchJsonContent(): Promise<TheiaDebugConfigurationModel.JsonContent & { uri: URI } | URI | undefined> {
92+
const sketch = await this.sketchesServiceClient.currentSketch();
93+
if (!sketch) {
94+
return undefined;
95+
}
96+
const uri = await this.sketchesService.getIdeTempFolderUri(sketch);
97+
const tempFolderUri = new URI(uri);
98+
await this.fileService.createFolder(tempFolderUri);
99+
try {
100+
const uri = tempFolderUri.resolve('launch.json');
101+
const { value } = await this.fileService.read(uri);
102+
const configurations = DebugConfigurationModel.parse(JSON.parse(value));
103+
return { uri, configurations };
104+
} catch (err) {
105+
if (err instanceof FileOperationError && err.fileOperationResult === FileOperationResult.FILE_NOT_FOUND) {
106+
return tempFolderUri;
107+
}
108+
console.error('Could not load debug configuration from IDE2 temp folder.', err);
109+
throw err;
110+
}
111+
}
112+
113+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { Event } from '@theia/core/lib/common/event';
2+
import URI from '@theia/core/lib/common/uri';
3+
import { PreferenceService } from '@theia/core/lib/browser/preferences/preference-service';
4+
import { DebugConfiguration } from '@theia/debug/lib/common/debug-common';
5+
import { DebugConfigurationModel as TheiaDebugConfigurationModel } from '@theia/debug/lib/browser/debug-configuration-model';
6+
7+
export class DebugConfigurationModel extends TheiaDebugConfigurationModel {
8+
9+
constructor(
10+
readonly workspaceFolderUri: string,
11+
protected readonly preferences: PreferenceService,
12+
protected readonly config: DebugConfiguration[],
13+
protected configUri: URI | undefined,
14+
protected readonly onConfigDidChange: Event<TheiaDebugConfigurationModel.JsonContent>) {
15+
16+
super(workspaceFolderUri, preferences);
17+
this.toDispose.push(onConfigDidChange(content => {
18+
const { uri, configurations } = content;
19+
this.configUri = uri;
20+
this.config.length = 0;
21+
this.config.push(...configurations);
22+
this.reconcile();
23+
}));
24+
this.reconcile();
25+
}
26+
27+
protected parseConfigurations(): TheiaDebugConfigurationModel.JsonContent {
28+
return {
29+
uri: this.configUri,
30+
configurations: this.config
31+
};
32+
}
33+
34+
}
35+
36+
export namespace DebugConfigurationModel {
37+
export function parse(launchConfig: any): DebugConfiguration[] {
38+
const configurations: DebugConfiguration[] = [];
39+
if (launchConfig && typeof launchConfig === 'object' && 'configurations' in launchConfig) {
40+
if (Array.isArray(launchConfig.configurations)) {
41+
for (const configuration of launchConfig.configurations) {
42+
if (DebugConfiguration.is(configuration)) {
43+
configurations.push(configuration);
44+
}
45+
}
46+
}
47+
}
48+
return configurations;
49+
}
50+
}

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

+6
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ export interface SketchesService {
6363
*/
6464
archive(sketch: Sketch, destinationUri: string): Promise<string>;
6565

66+
/**
67+
* Counterpart of the CLI's `genBuildPath` functionality.
68+
* Based on https://github.com/arduino/arduino-cli/blob/550179eefd2d2bca299d50a4af9e9bfcfebec649/arduino/builder/builder.go#L30-L38
69+
*/
70+
getIdeTempFolderUri(sketch: Sketch): Promise<string>;
71+
6672
}
6773

6874
export interface Sketch {

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

+13-22
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as fs from 'fs';
33
import * as os from 'os';
44
import * as temp from 'temp';
55
import * as path from 'path';
6+
import * as crypto from 'crypto';
67
import { ncp } from 'ncp';
78
import { promisify } from 'util';
89
import URI from '@theia/core/lib/common/uri';
@@ -13,22 +14,19 @@ import { SketchesService, Sketch } from '../common/protocol/sketches-service';
1314
import { firstToLowerCase } from '../common/utils';
1415
import { NotificationServiceServerImpl } from './notification-service-server';
1516
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
16-
import { CoreClientProvider } from './core-client-provider';
17+
import { CoreClientAware } from './core-client-provider';
1718
import { LoadSketchReq, ArchiveSketchReq } from './cli-protocol/commands/commands_pb';
1819

1920
const WIN32_DRIVE_REGEXP = /^[a-zA-Z]:\\/;
2021

2122
const prefix = '.arduinoIDE-unsaved';
2223

2324
@injectable()
24-
export class SketchesServiceImpl implements SketchesService {
25+
export class SketchesServiceImpl extends CoreClientAware implements SketchesService {
2526

2627
@inject(ConfigService)
2728
protected readonly configService: ConfigService;
2829

29-
@inject(CoreClientProvider)
30-
protected readonly coreClientProvider: CoreClientProvider;
31-
3230
@inject(NotificationServiceServerImpl)
3331
protected readonly notificationService: NotificationServiceServerImpl;
3432

@@ -348,23 +346,16 @@ void loop() {
348346
return destinationUri;
349347
}
350348

351-
private async coreClient(): Promise<CoreClientProvider.Client> {
352-
const coreClient = await new Promise<CoreClientProvider.Client>(async resolve => {
353-
const client = await this.coreClientProvider.client();
354-
if (client) {
355-
resolve(client);
356-
return;
357-
}
358-
const toDispose = this.coreClientProvider.onClientReady(async () => {
359-
const client = await this.coreClientProvider.client();
360-
if (client) {
361-
toDispose.dispose();
362-
resolve(client);
363-
return;
364-
}
365-
});
366-
});
367-
return coreClient;
349+
async getIdeTempFolderUri(sketch: Sketch): Promise<string> {
350+
const genBuildPath = await this.getIdeTempFolderPath(sketch);
351+
return FileUri.create(genBuildPath).toString();
352+
}
353+
354+
async getIdeTempFolderPath(sketch: Sketch): Promise<string> {
355+
const sketchPath = FileUri.fsPath(sketch.uri);
356+
await fs.promises.readdir(sketchPath); // Validates the sketch folder and rejects if not accessible.
357+
const suffix = crypto.createHash('md5').update(sketchPath).digest('hex');
358+
return path.join(os.tmpdir(), `arduino-ide2-${suffix}`);
368359
}
369360

370361
}

0 commit comments

Comments
 (0)