Skip to content
This repository has been archived by the owner on Apr 4, 2023. It is now read-only.

Commit

Permalink
Export configurations of Che tasks to config file (#295)
Browse files Browse the repository at this point in the history
* Export configurations of Che tasks to config file

Signed-off-by: Roman Nikitenko <rnikiten@redhat.com>
  • Loading branch information
RomanNikitenko authored Aug 21, 2019
1 parent ec29e99 commit e9fcf75
Show file tree
Hide file tree
Showing 14 changed files with 438 additions and 119 deletions.
3 changes: 2 additions & 1 deletion plugins/containers-plugin/src/containers-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@ export class ContainersService {
}
if (runtime.commands) {
container.commands = [];
runtime.commands.forEach(command => {
const cheCommands = runtime.commands.filter(command => command.type === 'exec');
cheCommands.forEach(command => {
if (command.attributes && command.attributes.machineName && command.attributes.machineName !== name) {
return;
}
Expand Down
10 changes: 9 additions & 1 deletion plugins/containers-plugin/src/containers-tree-data-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export class ContainersTreeDataProvider implements theia.TreeDataProvider<ITreeN
name: commandName,
tooltip: 'execute the command',
iconPath: 'fa-cogs medium-yellow',
command: { id: 'task:run', arguments: ['che', commandName] }
command: { id: 'task:run', arguments: [this.getRootPath(), commandName] }
});
});
}
Expand Down Expand Up @@ -214,6 +214,14 @@ export class ContainersTreeDataProvider implements theia.TreeDataProvider<ITreeN
this.onDidChangeTreeDataEmitter.fire();
}

private getRootPath(): string {
const workspaceFolders = theia.workspace.workspaceFolders;
if (!workspaceFolders || workspaceFolders.length < 1) {
return '/projects';
}
return workspaceFolders[0].uri.path;
}

private getRandId(): string {
let uniqueId = '';
for (let counter = 0; counter < 1000; counter++) {
Expand Down
12 changes: 11 additions & 1 deletion plugins/task-plugin/src/che-task-backend-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ import { PreviewUrlOpenService } from './preview/preview-url-open-service';
import { CheWorkspaceClient } from './che-workspace-client';
import { LaunchConfigurationsExporter } from './export/launch-configs-exporter';
import { TaskConfigurationsExporter } from './export/task-configs-exporter';
import { ConfigurationsExporter, ExportConfigurationsManager } from './export/export-configs-manager';
import { ExportConfigurationsManager, ConfigurationsExporter } from './export/export-configs-manager';
import { CheTaskConfigsExtractor } from './extract/che-task-configs-extractor';
import { ConfigFileLaunchConfigsExtractor } from './extract/config-file-launch-configs-extractor';
import { ConfigFileTasksExtractor } from './extract/config-file-task-configs-extractor';
import { VsCodeLaunchConfigsExtractor } from './extract/vscode-launch-configs-extractor';
import { VsCodeTaskConfigsExtractor } from './extract/vscode-task-configs-extractor';

const container = new Container();
container.bind(CheTaskProvider).toSelf().inSingletonScope();
Expand All @@ -42,6 +47,11 @@ container.bind(PreviewUrlOpenService).toSelf().inSingletonScope();
container.bind<ConfigurationsExporter>(ConfigurationsExporter).to(TaskConfigurationsExporter).inSingletonScope();
container.bind<ConfigurationsExporter>(ConfigurationsExporter).to(LaunchConfigurationsExporter).inSingletonScope();
container.bind(ExportConfigurationsManager).toSelf().inSingletonScope();
container.bind(CheTaskConfigsExtractor).toSelf().inSingletonScope();
container.bind(ConfigFileTasksExtractor).toSelf().inSingletonScope();
container.bind(ConfigFileLaunchConfigsExtractor).toSelf().inSingletonScope();
container.bind(VsCodeLaunchConfigsExtractor).toSelf().inSingletonScope();
container.bind(VsCodeTaskConfigsExtractor).toSelf().inSingletonScope();

container.bind(PreviewUrlsWidget).toSelf().inTransientScope();
container.bind(PreviewUrlsWidgetFactory).toDynamicValue(ctx => ({
Expand Down
47 changes: 15 additions & 32 deletions plugins/task-plugin/src/export/export-configs-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,21 @@ export const ConfigurationsExporter = Symbol('ConfigurationsExporter');
/** Exports content with configurations in the config file */
export interface ConfigurationsExporter {

/** Type of the exporter corresponds to type of command which brings content with configs */
readonly type: string;

/**
* Exports given content with configurations in the config file of given workspace folder
* @param configsContent content with configurations for export
* Exports configurations in the config file of given workspace folder
* @param workspaceFolder workspace folder for exporting configs in the config file
* @param commands commands with configurations for export
*/
export(configsContent: string, workspaceFolder: theia.WorkspaceFolder): void;
export(workspaceFolder: theia.WorkspaceFolder, commands: cheApi.workspace.Command[]): void;
}
/** Contains configurations as array of object and as raw content and is used at getting configurations from config file for example */
export interface Configurations<T> {

/** Raw content with configurations from config file */
content: string;

/** Configurations as array of objects */
configs: T[];
}

/** Reads the commands from the current Che workspace and exports task and launch configurations in the config files. */
Expand All @@ -47,36 +53,13 @@ export class ExportConfigurationsManager {

const cheCommands = await this.cheWorkspaceClient.getCommands();
for (const exporter of this.exporters) {
const configsContent = this.extractConfigsContent(exporter.type, cheCommands);
if (!configsContent) {
continue;
}

this.exportContent(configsContent, exporter, workspaceFolders);
this.doExport(workspaceFolders, cheCommands, exporter);
}
}

private exportContent(configsContent: string, exporter: ConfigurationsExporter, workspaceFolders: theia.WorkspaceFolder[]) {
private doExport(workspaceFolders: theia.WorkspaceFolder[], cheCommands: cheApi.workspace.Command[], exporter: ConfigurationsExporter) {
for (const workspaceFolder of workspaceFolders) {
exporter.export(configsContent, workspaceFolder);
exporter.export(workspaceFolder, cheCommands);
}
}

private extractConfigsContent(type: string, commands: cheApi.workspace.Command[]): string {
const configCommands = commands.filter(command => command.type === type);
if (configCommands.length === 0) {
return '';
}

if (configCommands.length > 1) {
console.warn(`Found duplicate entry for type ${type}`);
}

const configCommand = configCommands[0];
if (!configCommand || !configCommand.attributes || !configCommand.attributes.actionReferenceContent) {
return '';
}

return configCommand.attributes.actionReferenceContent;
}
}
86 changes: 61 additions & 25 deletions plugins/task-plugin/src/export/launch-configs-exporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,57 +8,93 @@
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/

import { injectable } from 'inversify';
import { injectable, inject } from 'inversify';
import * as theia from '@theia/plugin';
import { che as cheApi } from '@eclipse-che/api';
import { resolve } from 'path';
import { readFileSync, writeFileSync, format, modify, parse } from '../utils';
import { writeFileSync, modify } from '../utils';
import { ConfigurationsExporter } from './export-configs-manager';
import { ConfigFileLaunchConfigsExtractor } from '../extract/config-file-launch-configs-extractor';
import { VsCodeLaunchConfigsExtractor } from '../extract/vscode-launch-configs-extractor';

const CONFIG_DIR = '.theia';
const LAUNCH_CONFIG_FILE = 'launch.json';
const formattingOptions = { tabSize: 4, insertSpaces: true, eol: '' };

export const VSCODE_LAUNCH_TYPE = 'vscode-launch';

/** Exports content with launch configurations in the config file. */
@injectable()
export class LaunchConfigurationsExporter implements ConfigurationsExporter {
readonly type: string = VSCODE_LAUNCH_TYPE;

export(configsContent: string, workspaceFolder: theia.WorkspaceFolder): void {
@inject(ConfigFileLaunchConfigsExtractor)
protected readonly configFileLaunchConfigsExtractor: ConfigFileLaunchConfigsExtractor;

@inject(VsCodeLaunchConfigsExtractor)
protected readonly vsCodeLaunchConfigsExtractor: VsCodeLaunchConfigsExtractor;

export(workspaceFolder: theia.WorkspaceFolder, commands: cheApi.workspace.Command[]): void {
const launchConfigFileUri = this.getConfigFileUri(workspaceFolder.uri.path);
const existingContent = readFileSync(launchConfigFileUri);
if (configsContent === existingContent) {
return;
}
const configFileConfigs = this.configFileLaunchConfigsExtractor.extract(launchConfigFileUri);
const vsCodeConfigs = this.vsCodeLaunchConfigsExtractor.extract(commands);

const configsJson = parse(configsContent);
if (!configsJson || !configsJson.configurations) {
const configFileContent = configFileConfigs.content;
if (configFileContent) {
this.saveConfigs(launchConfigFileUri, configFileContent, this.merge(configFileConfigs.configs, vsCodeConfigs.configs, this.getConsoleConflictLogger()));
return;
}

const existingJson = parse(existingContent);
if (!existingJson || !existingJson.configurations) {
writeFileSync(launchConfigFileUri, format(configsContent, formattingOptions));
return;
const vsCodeConfigsContent = vsCodeConfigs.content;
if (vsCodeConfigsContent) {
this.saveConfigs(launchConfigFileUri, vsCodeConfigsContent, vsCodeConfigs.configs);
}

const mergedConfigs = this.merge(existingJson.configurations, configsJson.configurations);
const result = modify(configsContent, ['configurations'], mergedConfigs, formattingOptions);
writeFileSync(launchConfigFileUri, result);
}

private merge(existingConfigs: theia.DebugConfiguration[], newConfigs: theia.DebugConfiguration[]): theia.DebugConfiguration[] {
const result: theia.DebugConfiguration[] = Object.assign([], newConfigs);
for (const existing of existingConfigs) {
if (!newConfigs.some(config => config.name === existing.name)) {
result.push(existing);
private merge(configurations1: theia.DebugConfiguration[],
configurations2: theia.DebugConfiguration[],
conflictHandler: (config1: theia.DebugConfiguration, config2: theia.DebugConfiguration) => void): theia.DebugConfiguration[] {

const result: theia.DebugConfiguration[] = Object.assign([], configurations1);

for (const config2 of configurations2) {
const conflict = configurations1.find(config1 => config1.name === config2.name);
if (!conflict) {
result.push(config2);
continue;
}

if (this.areEqual(config2, conflict)) {
continue;
}

conflictHandler(conflict, config2);
}

return result;
}

private areEqual(config1: theia.DebugConfiguration, config2: theia.DebugConfiguration): boolean {
const { type: type1, name: name1, request: request1, ...properties1 } = config1;
const { type: type2, name: name2, request: request2, ...properties2 } = config2;

if (type1 !== type2 || name1 !== name2 || request1 !== request2) {
return false;
}

return JSON.stringify(properties1) === JSON.stringify(properties2);
}

private getConfigFileUri(rootDir: string): string {
return resolve(rootDir.toString(), CONFIG_DIR, LAUNCH_CONFIG_FILE);
}

private saveConfigs(launchConfigFileUri: string, content: string, configurations: theia.DebugConfiguration[]): void {
const result = modify(content, ['configurations'], configurations, formattingOptions);
writeFileSync(launchConfigFileUri, result);
}

private getConsoleConflictLogger(): (config1: theia.DebugConfiguration, config2: theia.DebugConfiguration) => void {
return (config1: theia.DebugConfiguration, config2: theia.DebugConfiguration) => {
console.warn(`Conflict at exporting launch configurations: ${JSON.stringify(config1)} and ${JSON.stringify(config2)}`,
`The configuration: ${JSON.stringify(config2)} is ignored`);
};
}
}
102 changes: 81 additions & 21 deletions plugins/task-plugin/src/export/task-configs-exporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,17 @@
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/

import { injectable } from 'inversify';
import { injectable, inject } from 'inversify';
import * as theia from '@theia/plugin';
import * as startPoint from '../task-plugin-backend';
import { che as cheApi } from '@eclipse-che/api';
import { TaskConfiguration } from '@eclipse-che/plugin';
import { resolve } from 'path';
import { readFileSync, writeFileSync, format, modify, parse } from '../utils';
import { writeFileSync, modify } from '../utils';
import { CheTaskConfigsExtractor } from '../extract/che-task-configs-extractor';
import { VsCodeTaskConfigsExtractor } from '../extract/vscode-task-configs-extractor';
import { ConfigurationsExporter } from './export-configs-manager';
import { ConfigFileTasksExtractor } from '../extract/config-file-task-configs-extractor';

const CONFIG_DIR = '.theia';
const TASK_CONFIG_FILE = 'tasks.json';
Expand All @@ -24,42 +29,97 @@ export const VSCODE_TASK_TYPE = 'vscode-task';
/** Exports configurations of tasks in the config file. */
@injectable()
export class TaskConfigurationsExporter implements ConfigurationsExporter {
readonly type: string = VSCODE_TASK_TYPE;

export(tasksContent: string, workspaceFolder: theia.WorkspaceFolder): void {
@inject(ConfigFileTasksExtractor)
protected readonly configFileTasksExtractor: ConfigFileTasksExtractor;

@inject(CheTaskConfigsExtractor)
protected readonly cheTaskConfigsExtractor: CheTaskConfigsExtractor;

@inject(VsCodeTaskConfigsExtractor)
protected readonly vsCodeTaskConfigsExtractor: VsCodeTaskConfigsExtractor;

export(workspaceFolder: theia.WorkspaceFolder, commands: cheApi.workspace.Command[]): void {
const tasksConfigFileUri = this.getConfigFileUri(workspaceFolder.uri.path);
const existingContent = readFileSync(tasksConfigFileUri);
if (tasksContent === existingContent) {
return;
}
const configFileTasks = this.configFileTasksExtractor.extract(tasksConfigFileUri);

const cheTasks = this.cheTaskConfigsExtractor.extract(commands);
const vsCodeTasks = this.vsCodeTaskConfigsExtractor.extract(commands);
const devfileConfigs = this.merge(cheTasks, vsCodeTasks.configs, this.getOutputChannelConflictLogger());

const tasksJson = parse(tasksContent);
if (!tasksJson || !tasksJson.tasks) {
const configFileContent = configFileTasks.content;
if (configFileContent) {
this.saveConfigs(tasksConfigFileUri, configFileContent, this.merge(configFileTasks.configs, devfileConfigs, this.getConsoleConflictLogger()));
return;
}

const existingJson = parse(existingContent);
if (!existingJson || !existingJson.tasks) {
writeFileSync(tasksConfigFileUri, format(tasksContent, formattingOptions));
const vsCodeTasksContent = vsCodeTasks.content;
if (vsCodeTasksContent) {
this.saveConfigs(tasksConfigFileUri, vsCodeTasksContent, devfileConfigs);
return;
}

const mergedConfigs = this.merge(existingJson.tasks, tasksJson.tasks);
const result = modify(tasksContent, ['tasks'], mergedConfigs, formattingOptions);
writeFileSync(tasksConfigFileUri, result);
if (cheTasks) {
this.saveConfigs(tasksConfigFileUri, '', cheTasks);
}
}

private merge(existingConfigs: TaskConfiguration[], newConfigs: TaskConfiguration[]): TaskConfiguration[] {
const result: TaskConfiguration[] = Object.assign([], newConfigs);
for (const existing of existingConfigs) {
if (!newConfigs.some(config => config.label === existing.label)) {
result.push(existing);
private merge(configurations1: TaskConfiguration[],
configurations2: TaskConfiguration[],
conflictHandler: (config1: TaskConfiguration, config2: TaskConfiguration) => void): TaskConfiguration[] {

const result: TaskConfiguration[] = Object.assign([], configurations1);

for (const config2 of configurations2) {
const conflict = configurations1.find(config1 => config1.label === config2.label);
if (!conflict) {
result.push(config2);
continue;
}

if (this.areEqual(config2, conflict)) {
continue;
}

conflictHandler(conflict, config2);
}

return result;
}

private areEqual(config1: TaskConfiguration, config2: TaskConfiguration): boolean {
const { type: type1, label: label1, ...properties1 } = config1;
const { type: type2, label: label2, ...properties2 } = config2;

if (type1 !== type2 || label1 !== label2) {
return false;
}

return JSON.stringify(properties1) === JSON.stringify(properties2);
}

private getConfigFileUri(rootDir: string): string {
return resolve(rootDir.toString(), CONFIG_DIR, TASK_CONFIG_FILE);
}

private saveConfigs(tasksConfigFileUri: string, content: string, configurations: TaskConfiguration[]): void {
const result = modify(content, ['tasks'], configurations, formattingOptions);
writeFileSync(tasksConfigFileUri, result);
}

private getOutputChannelConflictLogger(): (config1: TaskConfiguration, config2: TaskConfiguration) => void {
return (config1: TaskConfiguration, config2: TaskConfiguration) => {
const outputChannel = startPoint.getOutputChannel();
outputChannel.show();
outputChannel.appendLine(`Conflict at exporting task configurations: ${JSON.stringify(config1)} and ${JSON.stringify(config2)}`);
outputChannel.appendLine(`The configuration: ${JSON.stringify(config2)} is ignored`);
};
}

private getConsoleConflictLogger(): (config1: TaskConfiguration, config2: TaskConfiguration) => void {
return (config1: TaskConfiguration, config2: TaskConfiguration) => {
console.warn(`Conflict at exporting task configurations: ${JSON.stringify(config1)} and ${JSON.stringify(config2)}`,
`The configuration: ${JSON.stringify(config2)} is ignored`);
};
}
}
Loading

0 comments on commit e9fcf75

Please sign in to comment.