Skip to content

Commit

Permalink
Generalize example generator and isolate examples (#63)
Browse files Browse the repository at this point in the history
* Centralize definition of examples
* Generalize example generator
* Introduce contribution point for examples
* Support merging of new launch and task configs with existing ones
* Add reveal navigator after project generation
* Enforce a target folder (no generation into root)

Contributed on behalf of STMicroelectronics
  • Loading branch information
planger authored Jul 20, 2023
1 parent 8c7c009 commit bc65f42
Show file tree
Hide file tree
Showing 77 changed files with 451 additions and 133 deletions.
2 changes: 1 addition & 1 deletion applications/browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
"cdt-cloud-blueprint-updater": "1.38.0",
"cdt-cloud-blueprint-product": "1.38.0",
"theia-traceviewer": "0.2.0-next.20230619144800.297af68.0",
"@eclipse-cdt-cloud/blueprint-example-generator": "1.38.0"
"@eclipse-cdt-cloud/blueprint-examples": "1.38.0"
},
"devDependencies": {
"@theia/cli": "1.38.0"
Expand Down
2 changes: 1 addition & 1 deletion applications/docker/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
"cdt-cloud-blueprint-product": "1.38.0",
"cdt-cloud-blueprint-launcher": "1.38.0",
"theia-traceviewer": "0.2.0-next.20230619144800.297af68.0",
"@eclipse-cdt-cloud/blueprint-example-generator": "1.38.0"
"@eclipse-cdt-cloud/blueprint-examples": "1.38.0"
},
"devDependencies": {
"@theia/cli": "1.38.0"
Expand Down
2 changes: 1 addition & 1 deletion applications/electron/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@
"cdt-cloud-blueprint-product": "1.38.0",
"cdt-cloud-blueprint-launcher": "1.38.0",
"theia-traceviewer": "0.2.0-next.20230619144800.297af68.0",
"@eclipse-cdt-cloud/blueprint-example-generator": "1.38.0"
"@eclipse-cdt-cloud/blueprint-examples": "1.38.0"
},
"devDependencies": {
"@theia/cli": "1.38.0",
Expand Down
5 changes: 2 additions & 3 deletions theia-extensions/blueprint-example-generator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@
"description": "Extension for generating examples in CDT Cloud Blueprint",
"dependencies": {
"@theia/core": "1.38.0",
"@theia/workspace": "1.38.0",
"@theia/filesystem": "1.38.0",
"@theia/editor": "1.38.0",
"@theia/debug": "1.38.0",
"@theia/task": "1.38.0",
"fs-extra": "^8.1.0",
"inversify": "^5.1.1"
},
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,11 @@
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import { CommandContribution } from '@theia/core';
import { WebSocketConnectionProvider } from '@theia/core/lib/browser';
import { ContainerModule } from '@theia/core/shared/inversify';
import { ExampleGeneratorService, EXAMPLE_GENERATOR_PATH } from '../common/protocol';
import { ExampleGeneratorCommandContribution, GenerateExampleCommandHandler } from './example-generator-contribution';
import { EXAMPLE_GENERATOR_PATH, ExampleGeneratorService } from '../common/protocol';

export default new ContainerModule((bind, _unbind, isBound, rebind) => {
bind(GenerateExampleCommandHandler).toSelf().inSingletonScope();
bind(CommandContribution).to(ExampleGeneratorCommandContribution).inSingletonScope();
bind(ExampleGeneratorService).toDynamicValue(ctx => {
const connection = ctx.container.get(WebSocketConnectionProvider);
return connection.createProxy<ExampleGeneratorService>(EXAMPLE_GENERATOR_PATH);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/********************************************************************************
* Copyright (C) 2022 STMicroelectronics and others.
* Copyright (C) 2023 STMicroelectronics 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
Expand All @@ -14,5 +14,4 @@
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

export { GenerateExampleCommand } from './example-generator-contribution';
export { Examples } from '../common/protocol';
export { Example, ExampleGeneratorService } from '../common/protocol';
42 changes: 32 additions & 10 deletions theia-extensions/blueprint-example-generator/src/common/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,46 @@
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import { DebugConfiguration } from '@theia/debug/lib/common/debug-configuration';
import { TaskCustomization } from '@theia/task/lib/common/task-protocol';

export const ExampleGeneratorService = Symbol('ExampleGeneratorService');
export const EXAMPLE_GENERATOR_PATH = '/services/example-generator';

export enum Examples {
CMAKE_EXAMPLE = 'cmake-example',
EXAMPLE_TRACES = 'example-traces',
CLANGD_CONTEXTS = 'clangd-contexts'
export interface ExampleOptions {
/** The full path of the selected target folder. */
targetFolder: string;
/** The name of the target folder. */
targetFolderName: string;
}

export interface Example {
id: string;
label: string;
resourcesPath: string;
welcomeFile?: string;
tasks?: (options: ExampleOptions) => TaskCustomization[];
launches?: (options: ExampleOptions) => DebugConfiguration[];
}

/**
* Service for generating CDT Cloud Blueprint examples.
* Service for generating examples.
*/
export interface ExampleGeneratorService {
/**
* Generates the example with the name `exampleId` into the specified folder `targetFolderName`.
* @param exampleId identifyer of the example.
* @param targetFolderUri URI of the folder into which the example shall be generated.
* @returns (optional) URI of a file to be opened in an editor.
* Returns all examples available for generation.
*/
generateExample(exampleId: string, targetFolderUri: string): Promise<string | undefined>
getExamples(): Promise<Example[]>;
/**
* Generates the specified `example` into the specified folder `targetPath`.
* @param example the example to generate.
* @param targetPath URI of the folder into which the example shall be generated.
* @param targetFolderName The user-specified name of the target folder.
*/
generateExample(example: Example, targetPath: string, targetFolderName: string): Promise<void>
}

export const ExamplesContribution = Symbol('ExamplesContribution');
export interface ExamplesContribution {
readonly examples: Example[];
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import { ConnectionHandler, JsonRpcConnectionHandler } from '@theia/core';
import { ConnectionHandler, JsonRpcConnectionHandler, bindContributionProvider } from '@theia/core';
import { ContainerModule } from '@theia/core/shared/inversify';
import { ExampleGeneratorService, EXAMPLE_GENERATOR_PATH } from '../common/protocol';
import { EXAMPLE_GENERATOR_PATH, ExampleGeneratorService, ExamplesContribution } from '../common/protocol';
import { ExampleGeneratorServiceImpl } from './example-generator-service';

export default new ContainerModule((bind, unbind, isBound, rebind) => {
bindContributionProvider(bind, ExamplesContribution);
bind(ExampleGeneratorService).to(ExampleGeneratorServiceImpl).inSingletonScope();
bind(ConnectionHandler).toDynamicValue(ctx =>
new JsonRpcConnectionHandler(EXAMPLE_GENERATOR_PATH,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,53 +14,85 @@
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import { ContributionProvider } from '@theia/core';
import URI from '@theia/core/lib/common/uri';
import { FileUri } from '@theia/core/lib/node/file-uri';
import { injectable } from '@theia/core/shared/inversify';
import { inject, injectable, named } from '@theia/core/shared/inversify';
import * as fs from 'fs-extra';
import { ExampleGeneratorService, Examples } from '../common/protocol';

const EXAMPLE_DIRECTORY = 'resources';
import { Example, ExamplesContribution, ExampleGeneratorService } from '../common/protocol';
import { TaskConfiguration } from '@theia/task/lib/common';
import { DebugConfiguration } from '@theia/debug/lib/common/debug-configuration';

@injectable()
export class ExampleGeneratorServiceImpl implements ExampleGeneratorService {

async generateExample(exampleId: string, targetFolderUri: string): Promise<string | undefined> {
const examplesUri = new URI(module.path).resolve(`../../${EXAMPLE_DIRECTORY}`).normalizePath();
const examplesPath = FileUri.fsPath(examplesUri);
if (!examplesPath || !fs.existsSync(examplesPath)) {
throw new Error('Could not find examples folder');
}
@inject(ContributionProvider) @named(ExamplesContribution)
protected readonly examplesProvider: ContributionProvider<ExamplesContribution>;

async getExamples(): Promise<Example[]> {
const contributions = this.examplesProvider.getContributions();
return contributions.flatMap(contribution => contribution.examples);
}

const exampleUri = examplesUri.resolve(exampleId);
async generateExample(example: Example, target: string, folderName: string): Promise<void> {
const resolvedExample = (await this.getExamples()).find(e => e.id === example.id) ?? example;
const exampleUri = new URI(resolvedExample.resourcesPath);
const examplePath = FileUri.fsPath(exampleUri);
if (!examplePath || !fs.existsSync(examplePath)) {
throw new Error(`Could not find files in ${examplePath}`);
throw new Error(`Could not find resources of example in ${examplePath}`);
}

const targetUri = new URI(targetFolderUri);
const targetPath = FileUri.fsPath(targetUri);
fs.copySync(examplePath, targetPath, { recursive: true, errorOnExist: true });
const targetUri = new URI(target);
this.copyFiles(targetUri, examplePath);

const fileToBeOpened = this.getFileToBeOpened(exampleId);
if (fileToBeOpened) {
return FileUri.fsPath(targetUri.resolve(fileToBeOpened));
if (resolvedExample.tasks || resolvedExample.launches) {
const workspaceRoot = folderName ? targetUri.parent : targetUri;
const configFolder = FileUri.fsPath(workspaceRoot.resolve('.theia'));
fs.ensureDirSync(configFolder);
this.createOrAmendTasksJson(resolvedExample, workspaceRoot, folderName);
this.createOrAmendLaunchJson(resolvedExample, workspaceRoot, folderName);
}
}

return undefined;
protected copyFiles(targetUri: URI, examplePath: string): void {
const targetPath = FileUri.fsPath(targetUri);
fs.copySync(examplePath, targetPath, { recursive: true, errorOnExist: true });
}

protected getFileToBeOpened(exampleId: string): string | undefined {
if (exampleId === Examples.CMAKE_EXAMPLE) {
return 'CMAKE_EXAMPLE_README.md';
protected createOrAmendTasksJson(example: Example, workspaceRoot: URI, targetFolderName: string): void {
if (example.tasks) {
const tasksJsonPath = FileUri.fsPath(workspaceRoot.resolve('.theia/tasks.json'));
if (!fs.existsSync(tasksJsonPath)) {
fs.writeFileSync(tasksJsonPath, `{
"version": "2.0.0",
"tasks": []
}`);
}
const tasksJson = JSON.parse(fs.readFileSync(tasksJsonPath).toString());
const existingTaskConfigurations = tasksJson['tasks'] as TaskConfiguration[];
const targetFolder = FileUri.fsPath(workspaceRoot.resolve(targetFolderName));
const newTasks = example.tasks({targetFolderName, targetFolder});
tasksJson['tasks'] = [...existingTaskConfigurations, ...newTasks];
fs.writeFileSync(tasksJsonPath, JSON.stringify(tasksJson, undefined, 2));
}
if (exampleId === Examples.CLANGD_CONTEXTS) {
return 'CLANGD_CONTEXTS_README.md';
}
if (exampleId === Examples.EXAMPLE_TRACES) {
return 'EXAMPLE_TRACES_README.md';
}

protected createOrAmendLaunchJson(example: Example, workspaceRoot: URI, targetFolderName: string): void {
if (example.launches) {
const launchJsonPath = FileUri.fsPath(workspaceRoot.resolve('.theia/launch.json'));
if (!fs.existsSync(launchJsonPath)) {
fs.writeFileSync(launchJsonPath, `{
"version": "2.0.0",
"configurations": []
}`);
}
const launchJson = JSON.parse(fs.readFileSync(launchJsonPath).toString());
const existingLaunchConfigurations = launchJson['configurations'] as DebugConfiguration[];
const targetFolder = FileUri.fsPath(workspaceRoot.resolve(targetFolderName));
const newLaunchConfigs = example.launches({targetFolderName, targetFolder});
launchJson['configurations'] = [...existingLaunchConfigurations, ...newLaunchConfigs];
fs.writeFileSync(launchJsonPath, JSON.stringify(launchJson, undefined, 2));
}
return undefined;
}

}
17 changes: 17 additions & 0 deletions theia-extensions/blueprint-example-generator/src/node/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/********************************************************************************
* Copyright (C) 2023 STMicroelectronics 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 WITH Classpath-exception-2.0
********************************************************************************/

export { Example, ExampleGeneratorService, ExamplesContribution, ExampleOptions } from '../common/protocol';
10 changes: 10 additions & 0 deletions theia-extensions/blueprint-examples/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/** @type {import('eslint').Linter.Config} */
module.exports = {
extends: [
'../../configs/build.eslintrc.json'
],
parserOptions: {
tsconfigRootDir: __dirname,
project: 'tsconfig.json'
}
};
53 changes: 53 additions & 0 deletions theia-extensions/blueprint-examples/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"name": "@eclipse-cdt-cloud/blueprint-examples",
"version": "1.38.0",
"description": "Extension for generating examples in CDT Cloud Blueprint",
"dependencies": {
"@theia/core": "1.38.0",
"@theia/workspace": "1.38.0",
"@theia/navigator": "1.38.0",
"@theia/task": "1.38.0",
"@theia/debug": "1.38.0",
"@theia/filesystem": "1.38.0",
"@theia/editor": "1.38.0",
"@eclipse-cdt-cloud/blueprint-example-generator": "1.38.0",
"inversify": "^5.1.1"
},
"devDependencies": {
"rimraf": "^2.7.1",
"tslint": "^5.12.0",
"typescript": "^4.5.5"
},
"theiaExtensions": [
{
"frontendElectron": "lib/browser/blueprint-examples-frontend-module",
"frontend": "lib/browser/blueprint-examples-frontend-module",
"backend": "lib/node/blueprint-examples-backend-module"
}
],
"keywords": [
"theia-extension"
],
"license": "EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0",
"repository": {
"type": "git",
"url": "https://github.com/eclipse-cdt-cloud/cdt-cloud-blueprint.git"
},
"bugs": {
"url": "https://github.com/eclipse-cdt-cloud/cdt-cloud-blueprint/issues"
},
"homepage": "https://www.eclipse.org/cdt-cloud/",
"files": [
"resources",
"lib",
"src"
],
"scripts": {
"prepare": "yarn clean && yarn build",
"clean": "rimraf lib *.tsbuildinfo",
"build": "tsc -b",
"lint": "eslint --ext js,jsx,ts,tsx src",
"lint:fix": "eslint --ext js,jsx,ts,tsx src --fix",
"update:next": "ts-node ../../scripts/update-theia-to-next.ts"
}
}
Loading

0 comments on commit bc65f42

Please sign in to comment.