ATL-972: Moved the './theia/launch.json' config into a temp folder.

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
This commit is contained in:
Akos Kitta 2021-03-02 09:48:52 +01:00 committed by Akos Kitta
parent acbd98d0f8
commit d648159f43
6 changed files with 193 additions and 25 deletions

View File

@ -145,6 +145,8 @@ import { OutputToolbarContribution } from './theia/output/output-toolbar-contrib
import { AddZipLibrary } from './contributions/add-zip-library';
import { WorkspaceVariableContribution as TheiaWorkspaceVariableContribution } from '@theia/workspace/lib/browser/workspace-variable-contribution';
import { WorkspaceVariableContribution } from './theia/workspace/workspace-variable-contribution';
import { DebugConfigurationManager } from './theia/debug/debug-configuration-manager';
import { DebugConfigurationManager as TheiaDebugConfigurationManager } from '@theia/debug/lib/browser/debug-configuration-manager';
const ElementQueries = require('css-element-queries/src/ElementQueries');
@ -394,6 +396,9 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
// To remove the `Run` menu item from the application menu.
bind(DebugFrontendApplicationContribution).toSelf().inSingletonScope();
rebind(TheiaDebugFrontendApplicationContribution).toService(DebugFrontendApplicationContribution);
// To be able to use a `launch.json` from outside of the workspace.
bind(DebugConfigurationManager).toSelf().inSingletonScope();
rebind(TheiaDebugConfigurationManager).toService(DebugConfigurationManager);
// Preferences
bindArduinoPreferences(bind);

View File

@ -106,9 +106,11 @@ export class Debug extends SketchContribution {
if (!sketch) {
return;
}
const [cliPath, sketchPath] = await Promise.all([
const ideTempFolderUri = await this.sketchService.getIdeTempFolderUri(sketch);
const [cliPath, sketchPath, configPath] = await Promise.all([
this.fileService.fsPath(new URI(executables.cliUri)),
this.fileService.fsPath(new URI(sketch.uri))
this.fileService.fsPath(new URI(sketch.uri)),
this.fileService.fsPath(new URI(ideTempFolderUri)),
])
const config = {
cliPath,
@ -116,7 +118,8 @@ export class Debug extends SketchContribution {
fqbn,
name
},
sketchPath
sketchPath,
configPath
};
return this.commandService.executeCommand('arduino.debug.start', config);
}

View File

@ -0,0 +1,113 @@
import debounce = require('p-debounce');
import { inject, injectable, postConstruct } from 'inversify';
import URI from '@theia/core/lib/common/uri';
import { Event, Emitter } from '@theia/core/lib/common/event';
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
import { DebugConfiguration } from '@theia/debug/lib/common/debug-common';
import { DebugConfigurationModel as TheiaDebugConfigurationModel } from '@theia/debug/lib/browser/debug-configuration-model';
import { DebugConfigurationManager as TheiaDebugConfigurationManager } from '@theia/debug/lib/browser/debug-configuration-manager';
import { SketchesService } from '../../../common/protocol';
import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl';
import { DebugConfigurationModel } from './debug-configuration-model';
import { FileOperationError, FileOperationResult } from '@theia/filesystem/lib/common/files';
@injectable()
export class DebugConfigurationManager extends TheiaDebugConfigurationManager {
@inject(SketchesService)
protected readonly sketchesService: SketchesService;
@inject(SketchesServiceClientImpl)
protected readonly sketchesServiceClient: SketchesServiceClientImpl;
@inject(FrontendApplicationStateService)
protected readonly appStateService: FrontendApplicationStateService;
protected onTempContentDidChangeEmitter = new Emitter<TheiaDebugConfigurationModel.JsonContent>();
get onTempContentDidChange(): Event<TheiaDebugConfigurationModel.JsonContent> {
return this.onTempContentDidChangeEmitter.event;
}
@postConstruct()
protected async init(): Promise<void> {
super.init();
this.appStateService.reachedState('ready').then(async () => {
const tempContent = await this.getTempLaunchJsonContent();
if (!tempContent) {
// No active sketch.
return;
}
// Watch the file of the container folder.
this.fileService.watch(tempContent instanceof URI ? tempContent : tempContent.uri);
// Use the normalized temp folder name. We cannot compare Theia URIs here.
// /var/folders/k3/d2fkvv1j16v3_rz93k7f74180000gn/T/arduino-ide2-a0337d47f86b24a51df3dbcf2cc17925/launch.json
// /private/var/folders/k3/d2fkvv1j16v3_rz93k7f74180000gn/T/arduino-ide2-A0337D47F86B24A51DF3DBCF2CC17925/launch.json
const tempFolderName = (tempContent instanceof URI ? tempContent : tempContent.uri.parent).path.base.toLowerCase();
this.fileService.onDidFilesChange(event => {
for (const { resource } of event.changes) {
if (resource.path.base === 'launch.json' && resource.parent.path.base.toLowerCase() === tempFolderName) {
this.getTempLaunchJsonContent().then(config => {
if (config && !(config instanceof URI)) {
this.onTempContentDidChangeEmitter.fire(config);
}
});
break;
}
}
});
this.updateModels();
});
}
protected updateModels = debounce(async () => {
await this.appStateService.reachedState('ready');
const roots = await this.workspaceService.roots;
const toDelete = new Set(this.models.keys());
for (const rootStat of roots) {
const key = rootStat.resource.toString();
toDelete.delete(key);
if (!this.models.has(key)) {
const tempContent = await this.getTempLaunchJsonContent();
if (!tempContent) {
continue;
}
const configurations: DebugConfiguration[] = tempContent instanceof URI ? [] : tempContent.configurations;
const uri = tempContent instanceof URI ? undefined : tempContent.uri;
const model = new DebugConfigurationModel(key, this.preferences, configurations, uri, this.onTempContentDidChange);
model.onDidChange(() => this.updateCurrent());
model.onDispose(() => this.models.delete(key));
this.models.set(key, model);
}
}
for (const uri of toDelete) {
const model = this.models.get(uri);
if (model) {
model.dispose();
}
}
this.updateCurrent();
}, 500);
protected async getTempLaunchJsonContent(): Promise<TheiaDebugConfigurationModel.JsonContent & { uri: URI } | URI | undefined> {
const sketch = await this.sketchesServiceClient.currentSketch();
if (!sketch) {
return undefined;
}
const uri = await this.sketchesService.getIdeTempFolderUri(sketch);
const tempFolderUri = new URI(uri);
await this.fileService.createFolder(tempFolderUri);
try {
const uri = tempFolderUri.resolve('launch.json');
const { value } = await this.fileService.read(uri);
const configurations = DebugConfigurationModel.parse(JSON.parse(value));
return { uri, configurations };
} catch (err) {
if (err instanceof FileOperationError && err.fileOperationResult === FileOperationResult.FILE_NOT_FOUND) {
return tempFolderUri;
}
console.error('Could not load debug configuration from IDE2 temp folder.', err);
throw err;
}
}
}

View File

@ -0,0 +1,50 @@
import { Event } from '@theia/core/lib/common/event';
import URI from '@theia/core/lib/common/uri';
import { PreferenceService } from '@theia/core/lib/browser/preferences/preference-service';
import { DebugConfiguration } from '@theia/debug/lib/common/debug-common';
import { DebugConfigurationModel as TheiaDebugConfigurationModel } from '@theia/debug/lib/browser/debug-configuration-model';
export class DebugConfigurationModel extends TheiaDebugConfigurationModel {
constructor(
readonly workspaceFolderUri: string,
protected readonly preferences: PreferenceService,
protected readonly config: DebugConfiguration[],
protected configUri: URI | undefined,
protected readonly onConfigDidChange: Event<TheiaDebugConfigurationModel.JsonContent>) {
super(workspaceFolderUri, preferences);
this.toDispose.push(onConfigDidChange(content => {
const { uri, configurations } = content;
this.configUri = uri;
this.config.length = 0;
this.config.push(...configurations);
this.reconcile();
}));
this.reconcile();
}
protected parseConfigurations(): TheiaDebugConfigurationModel.JsonContent {
return {
uri: this.configUri,
configurations: this.config
};
}
}
export namespace DebugConfigurationModel {
export function parse(launchConfig: any): DebugConfiguration[] {
const configurations: DebugConfiguration[] = [];
if (launchConfig && typeof launchConfig === 'object' && 'configurations' in launchConfig) {
if (Array.isArray(launchConfig.configurations)) {
for (const configuration of launchConfig.configurations) {
if (DebugConfiguration.is(configuration)) {
configurations.push(configuration);
}
}
}
}
return configurations;
}
}

View File

@ -63,6 +63,12 @@ export interface SketchesService {
*/
archive(sketch: Sketch, destinationUri: string): Promise<string>;
/**
* Counterpart of the CLI's `genBuildPath` functionality.
* Based on https://github.com/arduino/arduino-cli/blob/550179eefd2d2bca299d50a4af9e9bfcfebec649/arduino/builder/builder.go#L30-L38
*/
getIdeTempFolderUri(sketch: Sketch): Promise<string>;
}
export interface Sketch {

View File

@ -3,6 +3,7 @@ import * as fs from 'fs';
import * as os from 'os';
import * as temp from 'temp';
import * as path from 'path';
import * as crypto from 'crypto';
import { ncp } from 'ncp';
import { promisify } from 'util';
import URI from '@theia/core/lib/common/uri';
@ -13,7 +14,7 @@ import { SketchesService, Sketch } from '../common/protocol/sketches-service';
import { firstToLowerCase } from '../common/utils';
import { NotificationServiceServerImpl } from './notification-service-server';
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
import { CoreClientProvider } from './core-client-provider';
import { CoreClientAware } from './core-client-provider';
import { LoadSketchReq, ArchiveSketchReq } from './cli-protocol/commands/commands_pb';
const WIN32_DRIVE_REGEXP = /^[a-zA-Z]:\\/;
@ -21,14 +22,11 @@ const WIN32_DRIVE_REGEXP = /^[a-zA-Z]:\\/;
const prefix = '.arduinoIDE-unsaved';
@injectable()
export class SketchesServiceImpl implements SketchesService {
export class SketchesServiceImpl extends CoreClientAware implements SketchesService {
@inject(ConfigService)
protected readonly configService: ConfigService;
@inject(CoreClientProvider)
protected readonly coreClientProvider: CoreClientProvider;
@inject(NotificationServiceServerImpl)
protected readonly notificationService: NotificationServiceServerImpl;
@ -348,23 +346,16 @@ void loop() {
return destinationUri;
}
private async coreClient(): Promise<CoreClientProvider.Client> {
const coreClient = await new Promise<CoreClientProvider.Client>(async resolve => {
const client = await this.coreClientProvider.client();
if (client) {
resolve(client);
return;
}
const toDispose = this.coreClientProvider.onClientReady(async () => {
const client = await this.coreClientProvider.client();
if (client) {
toDispose.dispose();
resolve(client);
return;
}
});
});
return coreClient;
async getIdeTempFolderUri(sketch: Sketch): Promise<string> {
const genBuildPath = await this.getIdeTempFolderPath(sketch);
return FileUri.create(genBuildPath).toString();
}
async getIdeTempFolderPath(sketch: Sketch): Promise<string> {
const sketchPath = FileUri.fsPath(sketch.uri);
await fs.promises.readdir(sketchPath); // Validates the sketch folder and rejects if not accessible.
const suffix = crypto.createHash('md5').update(sketchPath).digest('hex');
return path.join(os.tmpdir(), `arduino-ide2-${suffix}`);
}
}