UNPKG

@theia/task

Version:

Theia - Task extension. This extension adds support for executing raw or terminal processes in the backend.

479 lines • 20.8 kB
"use strict"; // ***************************************************************************** // Copyright (C) 2017-2018 Ericsson and others. // // This program and the accompanying materials are made available under the // terms of the Eclipse Public License v. 2.0 which is available at // http://www.eclipse.org/legal/epl-2.0. // // This Source Code may also be made available under the following Secondary // Licenses when the conditions for such availability set forth in the Eclipse // Public License v. 2.0 are satisfied: GNU General Public License, version 2 // with the GNU Classpath Exception which is available at // https://www.gnu.org/software/classpath/license.html. // // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** Object.defineProperty(exports, "__esModule", { value: true }); exports.TaskConfigurations = void 0; const tslib_1 = require("tslib"); const inversify_1 = require("@theia/core/shared/inversify"); const common_1 = require("../common"); const task_definition_registry_1 = require("./task-definition-registry"); const provided_task_configurations_1 = require("./provided-task-configurations"); const task_configuration_manager_1 = require("./task-configuration-manager"); const task_schema_updater_1 = require("./task-schema-updater"); const task_source_resolver_1 = require("./task-source-resolver"); const common_2 = require("@theia/core/lib/common"); const browser_1 = require("@theia/workspace/lib/browser"); /** * Watches a tasks.json configuration file and provides a parsed version of the contained task configurations */ let TaskConfigurations = class TaskConfigurations { constructor() { this.toDispose = new common_2.DisposableCollection(); /** * Map of source (path of root folder that the task configs come from) and task config map. * For the inner map (i.e., task config map), the key is task label and value TaskConfiguration */ this.tasksMap = new Map(); /** * Map of source (path of root folder that the task configs come from) and task customizations map. */ this.taskCustomizationMap = new Map(); this.client = undefined; /** * Map of source (path of root folder that the task configs come from) and raw task configurations / customizations. * This map is used to store the data from `tasks.json` files in workspace. */ this.rawTaskConfigurations = new Map(); this.toDispose.push(common_2.Disposable.create(() => { this.tasksMap.clear(); this.taskCustomizationMap.clear(); this.rawTaskConfigurations.clear(); this.client = undefined; })); } init() { this.toDispose.push(this.taskConfigurationManager.onDidChangeTaskConfig(async (change) => { try { await this.onDidTaskFileChange([change]); if (this.client) { this.client.taskConfigurationChanged(this.getTaskLabels()); } } catch (err) { console.error(err); } })); this.reorganizeTasks(); this.toDispose.push(this.taskSchemaUpdater.onDidChangeTaskSchema(() => this.reorganizeTasks())); } setClient(client) { this.client = client; } dispose() { this.toDispose.dispose(); } /** returns the list of known task labels */ getTaskLabels() { return Array.from(this.tasksMap.values()).reduce((acc, labelConfigMap) => acc.concat(Array.from(labelConfigMap.keys())), []); } /** * returns a collection of known tasks, which includes: * - all the configured tasks in `tasks.json`, and * - the customized detected tasks. * * The invalid task configs are not returned. */ async getTasks(token) { const configuredTasks = Array.from(this.tasksMap.values()).reduce((acc, labelConfigMap) => acc.concat(Array.from(labelConfigMap.values())), []); const detectedTasksAsConfigured = []; for (const [rootFolder, customizations] of Array.from(this.taskCustomizationMap.entries())) { for (const customization of customizations) { // TODO: getTasksToCustomize() will ask all task providers to contribute tasks. Doing this in a loop is bad. const detected = await this.providedTaskConfigurations.getTaskToCustomize(token, customization, rootFolder); if (detected) { // there might be a provided task that has a different scope from the task we're inspecting detectedTasksAsConfigured.push({ ...detected, ...customization }); } } } return [...configuredTasks, ...detectedTasksAsConfigured]; } getRawTaskConfigurations(scope) { if (scope === undefined) { const tasks = []; for (const configs of this.rawTaskConfigurations.values()) { tasks.push(...configs); } return tasks; } const scopeKey = this.getKeyFromScope(scope); if (this.rawTaskConfigurations.has(scopeKey)) { return Array.from(this.rawTaskConfigurations.get(scopeKey).values()); } return []; } /** * returns a collection of invalid task configs as per the task schema defined in Theia. */ getInvalidTaskConfigurations() { const invalidTaskConfigs = []; for (const taskConfigs of this.rawTaskConfigurations.values()) { for (const taskConfig of taskConfigs) { const isValid = this.isTaskConfigValid(taskConfig); if (!isValid) { invalidTaskConfigs.push(taskConfig); } } } return invalidTaskConfigs; } /** returns the task configuration for a given label or undefined if none */ getTask(scope, taskLabel) { const labelConfigMap = this.tasksMap.get(this.getKeyFromScope(scope)); if (labelConfigMap) { return labelConfigMap.get(taskLabel); } } /** returns the customized task for a given label or undefined if none */ async getCustomizedTask(token, scope, taskLabel) { const customizations = this.taskCustomizationMap.get(this.getKeyFromScope(scope)); if (customizations) { const customization = customizations.find(cus => cus.label === taskLabel); if (customization) { const detected = await this.providedTaskConfigurations.getTaskToCustomize(token, customization, scope); if (detected) { return { ...detected, ...customization, type: detected.type }; } } } } /** removes tasks configured in the given task config file */ removeTasks(scope) { const source = this.getKeyFromScope(scope); this.tasksMap.delete(source); this.taskCustomizationMap.delete(source); } /** * Removes task customization objects found in the given task config file from the memory. * Please note: this function does not modify the task config file. */ removeTaskCustomizations(scope) { const source = this.getKeyFromScope(scope); this.taskCustomizationMap.delete(source); } /** * Returns the task customizations by type from a given root folder in the workspace. * @param type the type of task customizations * @param rootFolder the root folder to find task customizations from. If `undefined`, this function returns an empty array. */ getTaskCustomizations(type, scope) { const customizationInRootFolder = this.taskCustomizationMap.get(this.getKeyFromScope(scope)); if (customizationInRootFolder) { return customizationInRootFolder.filter(c => c.type === type); } else { return []; } } /** * Returns the customization object in `tasks.json` for the given task. Please note, this function * returns `undefined` if the given task is not a detected task, because configured tasks don't need * customization objects - users can modify its config directly in `tasks.json`. * @param taskConfig The task config, which could either be a configured task or a detected task. */ getCustomizationForTask(taskConfig) { if (!this.isDetectedTask(taskConfig)) { return undefined; } const customizationByType = this.getTaskCustomizations(taskConfig.type, taskConfig._scope) || []; const hasCustomization = customizationByType.length > 0; if (hasCustomization) { const taskDefinition = this.taskDefinitionRegistry.getDefinition(taskConfig); if (taskDefinition) { const required = taskDefinition.properties.required || []; // Only support having one customization per task. return customizationByType.find(customization => required.every(property => customization[property] === taskConfig[property])); } } return undefined; } /** * Called when a change, to a config file we watch, is detected. */ async onDidTaskFileChange(fileChanges) { for (const change of fileChanges) { if (change.type === 2 /* FileChangeType.DELETED */) { this.removeTasks(change.scope); } else { // re-parse the config file await this.refreshTasks(change.scope); } } } /** * Read the task configs from the task configuration manager, and updates the list of available tasks. */ async refreshTasks(scope) { await this.readTasks(scope); this.removeTasks(scope); this.removeTaskCustomizations(scope); this.reorganizeTasks(); } /** parses a config file and extracts the tasks launch configurations */ async readTasks(scope) { const rawConfigArray = this.taskConfigurationManager.getTasks(scope); const key = this.getKeyFromScope(scope); if (this.rawTaskConfigurations.has(key)) { this.rawTaskConfigurations.delete(key); } this.rawTaskConfigurations.set(key, rawConfigArray); return rawConfigArray; } async openUserTasks() { await this.taskConfigurationManager.openConfiguration(common_1.TaskScope.Global); } /** Adds given task to a config file and opens the file to provide ability to edit task configuration. */ async configure(token, task) { const scope = task._scope; if (scope === common_1.TaskScope.Global) { return this.openUserTasks(); } const workspace = this.workspaceService.workspace; if (!workspace) { return; } const configuredAndCustomizedTasks = await this.getTasks(token); if (!configuredAndCustomizedTasks.some(t => this.taskDefinitionRegistry.compareTasks(t, task))) { await this.saveTask(scope, task); } try { await this.taskConfigurationManager.openConfiguration(scope); } catch (e) { console.error(`Error occurred while opening 'tasks.json' in ${this.taskSourceResolver.resolve(task)}.`, e); } } getTaskCustomizationTemplate(task) { const definition = this.getTaskDefinition(task); if (!definition) { console.error('Detected / Contributed tasks should have a task definition.'); return; } const customization = { type: task.type, runOptions: task.runOptions }; definition.properties.all.forEach(p => { if (task[p] !== undefined) { customization[p] = task[p]; } }); if ('problemMatcher' in task) { const problemMatcher = []; if (Array.isArray(task.problemMatcher)) { problemMatcher.push(...task.problemMatcher.map(t => { if (typeof t === 'string') { return t; } else { return t.name; } })); } else if (typeof task.problemMatcher === 'string') { problemMatcher.push(task.problemMatcher); } else if (task.problemMatcher) { problemMatcher.push(task.problemMatcher.name); } customization.problemMatcher = problemMatcher.map(common_1.asVariableName); } if (task.group) { customization.group = task.group; } customization.label = task.label; return { ...customization }; } /** Writes the task to a config file. Creates a config file if this one does not exist */ saveTask(scope, task) { const { _source, $ident, ...preparedTask } = task; const customizedTaskTemplate = this.getTaskCustomizationTemplate(task) || preparedTask; return this.taskConfigurationManager.addTaskConfiguration(scope, customizedTaskTemplate); } /** * This function is called after a change in TaskDefinitionRegistry happens. * It checks all tasks that have been loaded, and re-organized them in `tasksMap` and `taskCustomizationMap`. */ reorganizeTasks() { const newTaskMap = new Map(); const newTaskCustomizationMap = new Map(); const addCustomization = (rootFolder, customization) => { if (newTaskCustomizationMap.has(rootFolder)) { newTaskCustomizationMap.get(rootFolder).push(customization); } else { newTaskCustomizationMap.set(rootFolder, [customization]); } }; const addConfiguredTask = (rootFolder, label, configuredTask) => { if (newTaskMap.has(rootFolder)) { newTaskMap.get(rootFolder).set(label, configuredTask); } else { const newConfigMap = new Map(); newConfigMap.set(label, configuredTask); newTaskMap.set(rootFolder, newConfigMap); } }; for (const [scopeKey, taskConfigs] of this.rawTaskConfigurations.entries()) { for (const taskConfig of taskConfigs) { const scope = this.getScopeFromKey(scopeKey); const isValid = this.isTaskConfigValid(taskConfig); if (!isValid) { continue; } const transformedTask = this.getTransformedRawTask(taskConfig, scope); if (this.isDetectedTask(transformedTask)) { addCustomization(scopeKey, transformedTask); } else { addConfiguredTask(scopeKey, transformedTask['label'], transformedTask); } } } this.taskCustomizationMap = newTaskCustomizationMap; this.tasksMap = newTaskMap; } getTransformedRawTask(rawTask, scope) { let taskConfig; if (this.isDetectedTask(rawTask)) { const def = this.getTaskDefinition(rawTask); taskConfig = { ...rawTask, _source: def.source, _scope: scope }; } else { taskConfig = { ...rawTask, _source: scope, _scope: scope }; } return { ...taskConfig, presentation: common_1.TaskOutputPresentation.fromJson(rawTask) }; } /** * Returns `true` if the given task configuration is valid as per the task schema defined in Theia * or contributed by Theia extensions and plugins, `false` otherwise. */ isTaskConfigValid(task) { return this.taskSchemaUpdater.validate({ tasks: [task] }); } /** * Updates the task config in the `tasks.json`. * The task config, together with updates, will be written into the `tasks.json` if it is not found in the file. * * @param task task that the updates will be applied to * @param update the updates to be applied */ // eslint-disable-next-line @typescript-eslint/no-explicit-any async updateTaskConfig(token, task, update) { const scope = task._scope; const configuredAndCustomizedTasks = await this.getTasks(token); if (configuredAndCustomizedTasks.some(t => this.taskDefinitionRegistry.compareTasks(t, task))) { // task is already in `tasks.json` const jsonTasks = this.taskConfigurationManager.getTasks(scope); if (jsonTasks) { const ind = jsonTasks.findIndex((t) => { if (t.type !== (task.type)) { return false; } const def = this.taskDefinitionRegistry.getDefinition(t); if (def) { return def.properties.all.every(p => t[p] === task[p]); } return t.label === task.label; }); jsonTasks[ind] = { ...jsonTasks[ind], ...update }; } this.taskConfigurationManager.setTaskConfigurations(scope, jsonTasks); } else { // task is not in `tasks.json` Object.keys(update).forEach(taskProperty => { task[taskProperty] = update[taskProperty]; }); this.saveTask(scope, task); } } getKeyFromScope(scope) { // Converting the enums to string will not yield a valid URI, so the keys will be distinct from any URI. return scope.toString(); } getScopeFromKey(key) { if (common_1.TaskScope.Global.toString() === key) { return common_1.TaskScope.Global; } else if (common_1.TaskScope.Workspace.toString() === key) { return common_1.TaskScope.Workspace; } else { return key; } } /** checks if the config is a detected / contributed task */ isDetectedTask(task) { const taskDefinition = this.getTaskDefinition(task); // it is considered as a customization if the task definition registry finds a def for the task configuration return !!taskDefinition; } getTaskDefinition(task) { return this.taskDefinitionRegistry.getDefinition(task); } }; exports.TaskConfigurations = TaskConfigurations; tslib_1.__decorate([ (0, inversify_1.inject)(browser_1.WorkspaceService), tslib_1.__metadata("design:type", browser_1.WorkspaceService) ], TaskConfigurations.prototype, "workspaceService", void 0); tslib_1.__decorate([ (0, inversify_1.inject)(task_definition_registry_1.TaskDefinitionRegistry), tslib_1.__metadata("design:type", task_definition_registry_1.TaskDefinitionRegistry) ], TaskConfigurations.prototype, "taskDefinitionRegistry", void 0); tslib_1.__decorate([ (0, inversify_1.inject)(provided_task_configurations_1.ProvidedTaskConfigurations), tslib_1.__metadata("design:type", provided_task_configurations_1.ProvidedTaskConfigurations) ], TaskConfigurations.prototype, "providedTaskConfigurations", void 0); tslib_1.__decorate([ (0, inversify_1.inject)(task_configuration_manager_1.TaskConfigurationManager), tslib_1.__metadata("design:type", task_configuration_manager_1.TaskConfigurationManager) ], TaskConfigurations.prototype, "taskConfigurationManager", void 0); tslib_1.__decorate([ (0, inversify_1.inject)(task_schema_updater_1.TaskSchemaUpdater), tslib_1.__metadata("design:type", task_schema_updater_1.TaskSchemaUpdater) ], TaskConfigurations.prototype, "taskSchemaUpdater", void 0); tslib_1.__decorate([ (0, inversify_1.inject)(task_source_resolver_1.TaskSourceResolver), tslib_1.__metadata("design:type", task_source_resolver_1.TaskSourceResolver) ], TaskConfigurations.prototype, "taskSourceResolver", void 0); tslib_1.__decorate([ (0, inversify_1.postConstruct)(), tslib_1.__metadata("design:type", Function), tslib_1.__metadata("design:paramtypes", []), tslib_1.__metadata("design:returntype", void 0) ], TaskConfigurations.prototype, "init", null); exports.TaskConfigurations = TaskConfigurations = tslib_1.__decorate([ (0, inversify_1.injectable)(), tslib_1.__metadata("design:paramtypes", []) ], TaskConfigurations); //# sourceMappingURL=task-configurations.js.map