From 79a16390c8e3d6d0b4e29dfe5acfa795657b2113 Mon Sep 17 00:00:00 2001 From: Enguerrand de Ribaucourt Date: Thu, 14 Dec 2023 17:47:59 +0100 Subject: [PATCH 1/9] Feature: Add devtool workspaces to bitbake scan result Use `devtool status` to get the list of devtool workspaces. --- .../ui/bitbake-recipes-view.test.ts | 3 ++- client/src/driver/BitBakeProjectScanner.ts | 20 ++++++++++++++++++- client/src/lib/src/types/BitbakeScanResult.ts | 6 ++++++ client/src/ui/BitbakeRecipesView.ts | 2 +- client/src/ui/BitbakeStatusBar.ts | 2 +- server/src/BitbakeProjectScannerClient.ts | 3 ++- server/src/__tests__/completions.test.ts | 3 ++- server/src/__tests__/definition.test.ts | 6 ++++-- 8 files changed, 37 insertions(+), 8 deletions(-) diff --git a/client/src/__tests__/unit-tests/ui/bitbake-recipes-view.test.ts b/client/src/__tests__/unit-tests/ui/bitbake-recipes-view.test.ts index f3f11a60..d1f90983 100644 --- a/client/src/__tests__/unit-tests/ui/bitbake-recipes-view.test.ts +++ b/client/src/__tests__/unit-tests/ui/bitbake-recipes-view.test.ts @@ -47,7 +47,8 @@ describe('BitbakeDriver Recipes View', () => { _includes: [], _layers: [], _classes: [], - _overrides: [] + _overrides: [], + _workspaces: [] } vscode.window.registerTreeDataProvider = jest.fn().mockImplementation( diff --git a/client/src/driver/BitBakeProjectScanner.ts b/client/src/driver/BitBakeProjectScanner.ts index 64172f7d..f8ff4c48 100644 --- a/client/src/driver/BitBakeProjectScanner.ts +++ b/client/src/driver/BitBakeProjectScanner.ts @@ -13,6 +13,7 @@ import { logger } from '../lib/src/utils/OutputLogger' import type { BitbakeScanResult, + DevtoolWorkspaceInfo, ElementInfo, LayerInfo, PathInfo @@ -38,7 +39,7 @@ export class BitBakeProjectScanner { private readonly _recipesFileExtension: string = 'bb' onChange: EventEmitter = new EventEmitter() - private readonly _bitbakeScanResult: BitbakeScanResult = { _classes: [], _includes: [], _layers: [], _overrides: [], _recipes: [] } + private readonly _bitbakeScanResult: BitbakeScanResult = { _classes: [], _includes: [], _layers: [], _overrides: [], _recipes: [], _workspaces: [] } private _shouldDeepExamine: boolean = false private _bitbakeDriver: BitbakeDriver | undefined private _languageClient: LanguageClient | undefined @@ -91,6 +92,7 @@ export class BitBakeProjectScanner { await this.scanForRecipes() await this.scanRecipesAppends() await this.scanOverrides() + await this.scanDevtoolWorkspaces() this.parseAllRecipes() logger.info('scan ready') @@ -165,6 +167,7 @@ export class BitBakeProjectScanner { logger.info(`Inc-Files: ${this._bitbakeScanResult._includes.length}`) logger.info(`bbclass: ${this._bitbakeScanResult._classes.length}`) logger.info(`overrides: ${this._bitbakeScanResult._overrides.length}`) + logger.info(`Devtool-workspaces: ${this._bitbakeScanResult._workspaces.length}`) } private scanForClasses (): void { @@ -315,6 +318,21 @@ export class BitBakeProjectScanner { this._bitbakeScanResult._overrides = output.match(outerReg)?.[1].split(':') ?? [] } + private async scanDevtoolWorkspaces (): Promise { + this._bitbakeScanResult._workspaces = new Array < DevtoolWorkspaceInfo >() + const commandResult = await this.executeBitBakeCommand('devtool status') + if (commandResult.status !== 0) { + logger.error(`Failed to scan devtool workspaces: ${commandResult.stderr.toString()}`) + return + } + const output = commandResult.output.toString() + const regex = /^([^\s]+):\s([^\s]+)$/gm + let match + while ((match = regex.exec(output)) !== null) { + this._bitbakeScanResult._workspaces.push({ name: match[1], path: match[2] }) + } + } + parseAllRecipes (): void { void vscode.commands.executeCommand('bitbake.parse-recipes') } diff --git a/client/src/lib/src/types/BitbakeScanResult.ts b/client/src/lib/src/types/BitbakeScanResult.ts index 8e4c793f..1d90d942 100644 --- a/client/src/lib/src/types/BitbakeScanResult.ts +++ b/client/src/lib/src/types/BitbakeScanResult.ts @@ -27,10 +27,16 @@ export interface ElementInfo { version?: string } +export interface DevtoolWorkspaceInfo { + name: string + path: string +} + export interface BitbakeScanResult { _layers: LayerInfo[] _classes: ElementInfo[] _includes: ElementInfo[] _recipes: ElementInfo[] _overrides: string[] + _workspaces: DevtoolWorkspaceInfo[] } diff --git a/client/src/ui/BitbakeRecipesView.ts b/client/src/ui/BitbakeRecipesView.ts index 388b78d9..8ed99290 100644 --- a/client/src/ui/BitbakeRecipesView.ts +++ b/client/src/ui/BitbakeRecipesView.ts @@ -50,7 +50,7 @@ class BitbakeTreeDataProvider implements vscode.TreeDataProvider = new vscode.EventEmitter() readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event private readonly bitbakeProjectScanner: BitBakeProjectScanner - private bitbakeScanResults: BitbakeScanResult = { _layers: [], _classes: [], _includes: [], _recipes: [], _overrides: [] } + private bitbakeScanResults: BitbakeScanResult = { _layers: [], _classes: [], _includes: [], _recipes: [], _overrides: [], _workspaces: [] } constructor (bitbakeWorkspace: BitbakeWorkspace, bitbakeProjectScanner: BitBakeProjectScanner) { this.bitbakeWorkspace = bitbakeWorkspace diff --git a/client/src/ui/BitbakeStatusBar.ts b/client/src/ui/BitbakeStatusBar.ts index 310a0673..aad3b008 100644 --- a/client/src/ui/BitbakeStatusBar.ts +++ b/client/src/ui/BitbakeStatusBar.ts @@ -10,7 +10,7 @@ import { type BitbakeCustomExecution } from './BitbakeTaskProvider' import { type BitBakeProjectScanner } from '../driver/BitBakeProjectScanner' export class BitbakeStatusBar { - private bitbakeScanResults: BitbakeScanResult = { _layers: [], _classes: [], _includes: [], _recipes: [], _overrides: [] } + private bitbakeScanResults: BitbakeScanResult = { _layers: [], _classes: [], _includes: [], _recipes: [], _overrides: [], _workspaces: [] } private readonly bitbakeProjectScanner: BitBakeProjectScanner readonly statusBarItem: vscode.StatusBarItem private scanInProgress = false diff --git a/server/src/BitbakeProjectScannerClient.ts b/server/src/BitbakeProjectScannerClient.ts index 742f1a0b..68b6bcc5 100644 --- a/server/src/BitbakeProjectScannerClient.ts +++ b/server/src/BitbakeProjectScannerClient.ts @@ -15,7 +15,8 @@ export class BitBakeProjectScannerClient { _classes: [], _includes: [], _recipes: [], - _overrides: [] + _overrides: [], + _workspaces: [] } private connection: Connection | undefined diff --git a/server/src/__tests__/completions.test.ts b/server/src/__tests__/completions.test.ts index 0854dad5..5101d1b2 100644 --- a/server/src/__tests__/completions.test.ts +++ b/server/src/__tests__/completions.test.ts @@ -645,7 +645,8 @@ describe('On Completion', () => { ], _layers: [], _overrides: [], - _recipes: [] + _recipes: [], + _workspaces: [] } await analyzer.analyze({ diff --git a/server/src/__tests__/definition.test.ts b/server/src/__tests__/definition.test.ts index 045ccfa6..7cbe1c60 100644 --- a/server/src/__tests__/definition.test.ts +++ b/server/src/__tests__/definition.test.ts @@ -54,7 +54,8 @@ describe('on definition', () => { ], _layers: [], _overrides: [], - _recipes: [] + _recipes: [], + _workspaces: [] } await analyzer.analyze({ @@ -116,7 +117,8 @@ describe('on definition', () => { path: parsedBarPath, extraInfo: 'layer: core' } - ] + ], + _workspaces: [] } await analyzer.analyze({ From 410ac4d766fbc4881a54966d70d8581454063bb0 Mon Sep 17 00:00:00 2001 From: Enguerrand de Ribaucourt Date: Thu, 14 Dec 2023 17:51:26 +0100 Subject: [PATCH 2/9] Ui: Rephrase some recipes related strings Preparing for new devtool commands, improve clarity or genericity of some presented UI elements. --- client/package.json | 5 +++-- client/src/ui/BitbakeCommands.ts | 4 ++-- client/src/ui/BitbakeRecipesView.ts | 1 + 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/client/package.json b/client/package.json index f9e1564f..5af0af58 100644 --- a/client/package.json +++ b/client/package.json @@ -300,8 +300,9 @@ "bitbakeView": [ { "id": "bitbakeRecipes", - "name": "Recipes", - "contextualTitle": "Recipes Explorer", + "name": "Recipes Explorer", + "contextualTitle": "Recipes explorer", + "icon": "$(library)", "when": "bitbake.active" } ] diff --git a/client/src/ui/BitbakeCommands.ts b/client/src/ui/BitbakeCommands.ts index 3a27f2f9..2624e467 100644 --- a/client/src/ui/BitbakeCommands.ts +++ b/client/src/ui/BitbakeCommands.ts @@ -141,12 +141,12 @@ async function selectRecipe (bitbakeWorkspace: BitbakeWorkspace, uri?: any, canC // No recipe is provided when calling the command through the command pallette if (chosenRecipe === undefined) { if (canCreate) { - chosenRecipe = await vscode.window.showQuickPick([...bitbakeWorkspace.activeRecipes, 'Add another recipe...'], { placeHolder: 'Select recipe to build' }) + chosenRecipe = await vscode.window.showQuickPick([...bitbakeWorkspace.activeRecipes, 'Add another recipe...'], { placeHolder: 'Select bitbake recipe' }) if (chosenRecipe === 'Add another recipe...') { chosenRecipe = await addActiveRecipe(bitbakeWorkspace) } } else { - chosenRecipe = await vscode.window.showQuickPick(bitbakeWorkspace.activeRecipes, { placeHolder: 'Select recipe to build' }) + chosenRecipe = await vscode.window.showQuickPick(bitbakeWorkspace.activeRecipes, { placeHolder: 'Select bitbake recipe' }) } } return chosenRecipe diff --git a/client/src/ui/BitbakeRecipesView.ts b/client/src/ui/BitbakeRecipesView.ts index 8ed99290..61abc975 100644 --- a/client/src/ui/BitbakeRecipesView.ts +++ b/client/src/ui/BitbakeRecipesView.ts @@ -125,6 +125,7 @@ class BitbakeTreeDataProvider implements vscode.TreeDataProvider Date: Thu, 14 Dec 2023 17:53:06 +0100 Subject: [PATCH 3/9] Feature: Add devtool workspaces view Present the scan results for devtool workspaces in a new view that will allow manipulating them. --- client/package.json | 23 ++++--- client/src/extension.ts | 4 ++ client/src/ui/DevtoolWorkspacesView.ts | 88 ++++++++++++++++++++++++++ 3 files changed, 107 insertions(+), 8 deletions(-) create mode 100644 client/src/ui/DevtoolWorkspacesView.ts diff --git a/client/package.json b/client/package.json index 5af0af58..274acbce 100644 --- a/client/package.json +++ b/client/package.json @@ -304,6 +304,13 @@ "contextualTitle": "Recipes explorer", "icon": "$(library)", "when": "bitbake.active" + }, + { + "id": "devtoolWorkspaces", + "name": "Devtool Workspaces", + "contextualTitle": "Devtool workspaces", + "icon": "$(symbol-property)", + "when": "bitbake.active" } ] }, @@ -359,28 +366,28 @@ "view/item/context": [ { "command": "bitbake.clean-recipe", - "group": "bitbake_recipe@1", - "when": "view == bitbakeRecipeCtx" + "group": "0@bitbake_recipe@1", + "when": "viewItem == bitbakeRecipeCtx || viewItem == devtoolWorskpaceCtx" }, { "command": "bitbake.build-recipe", - "group": "bitbake_recipe@0", - "when": "viewItem == bitbakeRecipeCtx" + "group": "0@bitbake_recipe@0", + "when": "viewItem == bitbakeRecipeCtx || viewItem == devtoolWorskpaceCtx" }, { "command": "bitbake.run-task", - "group": "bitbake_recipe@2", - "when": "viewItem == bitbakeRecipeCtx" + "group": "0@bitbake_recipe@2", + "when": "viewItem == bitbakeRecipeCtx || viewItem == devtoolWorskpaceCtx" }, { "command": "bitbake.drop-recipe", - "group": "bitbake_recipe@3", + "group": "0@bitbake_recipe@3", "when": "viewItem == bitbakeRecipeCtx" }, { "command": "bitbake.build-recipe", "group": "inline@0", - "when": "viewItem == bitbakeRecipeCtx" + "when": "viewItem == bitbakeRecipeCtx || viewItem == devtoolWorskpaceCtx" }, { "command": "bitbake.clean-recipe", diff --git a/client/src/extension.ts b/client/src/extension.ts index 29ba6c4a..b639f46e 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -17,6 +17,7 @@ import { BitbakeRecipesView } from './ui/BitbakeRecipesView' import { BitbakeStatusBar } from './ui/BitbakeStatusBar' import { bitBakeProjectScanner } from './driver/BitBakeProjectScanner' import { BitbakeDocumentLinkProvider } from './documentLinkProvider' +import { DevtoolWorkspacesView } from './ui/DevtoolWorkspacesView' let client: LanguageClient const bitbakeDriver: BitbakeDriver = new BitbakeDriver() @@ -26,6 +27,7 @@ const bitbakeWorkspace: BitbakeWorkspace = new BitbakeWorkspace() export let bitbakeExtensionContext: vscode.ExtensionContext // eslint-disable-next-line @typescript-eslint/no-unused-vars let bitbakeRecipesView: BitbakeRecipesView | undefined +let devtoolWorkspacesView: DevtoolWorkspacesView | undefined function loadLoggerSettings (): void { logger.level = vscode.workspace.getConfiguration('bitbake').get('loggingLevel') ?? 'info' @@ -70,6 +72,8 @@ export async function activate (context: vscode.ExtensionContext): Promise clientNotificationManager.setMemento(context.workspaceState) bitbakeRecipesView = new BitbakeRecipesView(bitbakeWorkspace, bitBakeProjectScanner) bitbakeRecipesView.registerView(context) + devtoolWorkspacesView = new DevtoolWorkspacesView(bitBakeProjectScanner) + devtoolWorkspacesView.registerView(context) void vscode.commands.executeCommand('setContext', 'bitbake.active', true) const bitbakeStatusBar = new BitbakeStatusBar(bitBakeProjectScanner) context.subscriptions.push(bitbakeStatusBar.statusBarItem) diff --git a/client/src/ui/DevtoolWorkspacesView.ts b/client/src/ui/DevtoolWorkspacesView.ts new file mode 100644 index 00000000..8f331d65 --- /dev/null +++ b/client/src/ui/DevtoolWorkspacesView.ts @@ -0,0 +1,88 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) 2023 Savoir-faire Linux. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + * ------------------------------------------------------------------------------------------ */ + +import * as vscode from 'vscode' +import { type BitbakeScanResult, type DevtoolWorkspaceInfo } from '../lib/src/types/BitbakeScanResult' +import { type BitBakeProjectScanner } from '../driver/BitBakeProjectScanner' + +export class DevtoolWorkspacesView { + private readonly devtoolTreeProvider: DevtoolTreeDataProvider + + constructor (bitbakeProjectScanner: BitBakeProjectScanner) { + this.devtoolTreeProvider = new DevtoolTreeDataProvider(bitbakeProjectScanner) + } + + registerView (context: vscode.ExtensionContext): void { + const view = vscode.window.createTreeView('devtoolWorkspaces', { treeDataProvider: this.devtoolTreeProvider, showCollapseAll: true }) + context.subscriptions.push(view) + vscode.window.registerTreeDataProvider('devtoolWorkspaces', this.devtoolTreeProvider) + } +} + +// TODO refactor common code between both views +class DevtoolTreeDataProvider implements vscode.TreeDataProvider { + private readonly _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter() + readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event + private readonly bitbakeProjectScanner: BitBakeProjectScanner + private bitbakeScanResults: BitbakeScanResult = { _layers: [], _classes: [], _includes: [], _recipes: [], _overrides: [], _workspaces: [] } + + constructor (bitbakeProjectScanner: BitBakeProjectScanner) { + this.bitbakeProjectScanner = bitbakeProjectScanner + + bitbakeProjectScanner.onChange.on('scanReady', (scanResults: BitbakeScanResult) => { + // In case a parsing error was just introduced, we keep the previous results to keep navigation functional + if (this.bitbakeScanResults._recipes.length === 0 || scanResults._recipes.length > 0) { + this.bitbakeScanResults = scanResults + } + this._onDidChangeTreeData.fire(undefined) + }) + } + + getTreeItem (element: DevtoolWorkspaceTreeItem): vscode.TreeItem | Thenable { + return element + } + + getChildren (element?: DevtoolWorkspaceTreeItem | undefined): vscode.ProviderResult { + if (element === undefined) { + const items = this.getDevtoolWorkspaces() + items.push(this.getAddWorkspaceItem()) + return items + } + + // No children for devtool workspaces + return [] + } + + private getDevtoolWorkspaces (): DevtoolWorkspaceTreeItem[] { + return this.bitbakeScanResults._workspaces.map((workspace: DevtoolWorkspaceInfo) => { + return new DevtoolWorkspaceTreeItem(workspace) + }) + } + + private getAddWorkspaceItem (): DevtoolWorkspaceTreeItem { + const item = new DevtoolWorkspaceTreeItem({ name: 'New devtool workspace', path: '' }) + item.command = { command: 'bitbake.devtool-modify', title: 'Open a new devtool workspace to modify a recipe\'s sources', arguments: [undefined] } + item.iconPath = new vscode.ThemeIcon('edit') + item.contextValue = undefined + item.tooltip = 'Open a new devtool workspace to modify a recipe\'s sources' + return item + } +} + +export class DevtoolWorkspaceTreeItem extends vscode.TreeItem { + constructor (public readonly workspace: DevtoolWorkspaceInfo) { + super(workspace.name, vscode.TreeItemCollapsibleState.None) + this.contextValue = 'devtoolWorskpaceCtx' + this.iconPath = new vscode.ThemeIcon('folder') + this.command = { + command: 'bitbake.devtool-open-workspace', + title: 'Open sources workspace in a new window', + arguments: [ + workspace.name + ] + } + this.tooltip = "Open the workspace's sources in a new window" + } +} From 619bb5e388a577c10d32dbb6842767ff87817fa7 Mon Sep 17 00:00:00 2001 From: Enguerrand de Ribaucourt Date: Thu, 14 Dec 2023 17:55:31 +0100 Subject: [PATCH 4/9] Feature: Add basic devtool commands Add commands to modify, update, reset and open devtool workspaces. --- client/package.json | 63 ++++++++++++++++++++-- client/src/extension.ts | 3 +- client/src/ui/BitbakeCommands.ts | 91 +++++++++++++++++++++++++++++++- 3 files changed, 151 insertions(+), 6 deletions(-) diff --git a/client/package.json b/client/package.json index 274acbce..23fb4dbc 100644 --- a/client/package.json +++ b/client/package.json @@ -285,6 +285,29 @@ "command": "bitbake.parse-recipes", "title": "BitBake: Parse all recipes", "description": "This command runs bitbake in parse-only mode." + }, + { + "command": "bitbake.devtool-modify", + "title": "BitBake: Devtool: Modify recipe", + "description": "Open a new devtool workspace to modify a recipe's sources." + }, + { + "command": "bitbake.devtool-open-workspace", + "title": "BitBake: Devtool: Open Workspace", + "description": "Open a devtool sources workspace in a new VSCode window.", + "icon": "$(file-symlink-directory)" + }, + { + "command": "bitbake.devtool-reset", + "title": "BitBake: Devtool: Reset", + "description": "Remove a devtool workspace.", + "icon": "$(trash)" + }, + { + "command": "bitbake.devtool-update", + "title": "BitBake: Devtool: Update recipe", + "description": "Update a recipe from a devtool workspace's modifications.", + "icon": "$(save-as)" } ], "viewsContainers": { @@ -324,15 +347,19 @@ "bitbake/main": [ { "command": "bitbake.clean-recipe", - "group": "bitbake_recipe@1" + "group": "0@bitbake_recipe@1" }, { "command": "bitbake.build-recipe", - "group": "bitbake_recipe@0" + "group": "0@bitbake_recipe@0" }, { "command": "bitbake.run-task", - "group": "bitbake_recipe@2" + "group": "0@bitbake_recipe@2" + }, + { + "command": "bitbake.devtool-modify", + "group": "1@bitbake_devtool@0" } ], "explorer/context": [ @@ -398,6 +425,36 @@ "command": "bitbake.drop-recipe", "group": "inline@2", "when": "viewItem == bitbakeRecipeCtx" + }, + { + "command": "bitbake.devtool-modify", + "group": "1@bitbake_devtool@0", + "when": "viewItem == bitbakeRecipeCtx" + }, + { + "command": "bitbake.devtool-open-workspace", + "group": "1@bitbake_devtool@1", + "when": "viewItem == devtoolWorskpaceCtx" + }, + { + "command": "bitbake.devtool-reset", + "group": "1@bitbake_devtool@3", + "when": "viewItem == devtoolWorskpaceCtx" + }, + { + "command": "bitbake.devtool-update", + "group": "1@bitbake_devtool@2", + "when": "viewItem == devtoolWorskpaceCtx" + }, + { + "command": "bitbake.devtool-reset", + "group": "inline@5", + "when": "viewItem == devtoolWorskpaceCtx" + }, + { + "command": "bitbake.devtool-update", + "group": "inline@4", + "when": "viewItem == devtoolWorskpaceCtx" } ] } diff --git a/client/src/extension.ts b/client/src/extension.ts index b639f46e..e79c83fa 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -11,7 +11,7 @@ import { logger } from './lib/src/utils/OutputLogger' import { activateLanguageServer, deactivateLanguageServer } from './language/languageClient' import { BitbakeDriver } from './driver/BitbakeDriver' import { BitbakeTaskProvider } from './ui/BitbakeTaskProvider' -import { registerBitbakeCommands } from './ui/BitbakeCommands' +import { registerBitbakeCommands, registerDevtoolCommands } from './ui/BitbakeCommands' import { BitbakeWorkspace } from './ui/BitbakeWorkspace' import { BitbakeRecipesView } from './ui/BitbakeRecipesView' import { BitbakeStatusBar } from './ui/BitbakeStatusBar' @@ -104,6 +104,7 @@ export async function activate (context: vscode.ExtensionContext): Promise })) registerBitbakeCommands(context, bitbakeWorkspace, bitbakeTaskProvider, bitBakeProjectScanner) + registerDevtoolCommands(context, bitbakeWorkspace, bitbakeDriver) logger.info('Congratulations, your extension "BitBake" is now active!') diff --git a/client/src/ui/BitbakeCommands.ts b/client/src/ui/BitbakeCommands.ts index 2624e467..43c7bccc 100644 --- a/client/src/ui/BitbakeCommands.ts +++ b/client/src/ui/BitbakeCommands.ts @@ -11,12 +11,14 @@ import { logger } from '../lib/src/utils/OutputLogger' import { type BitbakeWorkspace } from './BitbakeWorkspace' import path from 'path' import { BitbakeRecipeTreeItem } from './BitbakeRecipesView' -import { type BitBakeProjectScanner } from '../driver/BitBakeProjectScanner' +import { type BitBakeProjectScanner, bitBakeProjectScanner } from '../driver/BitBakeProjectScanner' import { extractRecipeName } from '../lib/src/utils/files' -import { runBitbakeTerminal } from './BitbakeTerminal' +import { runBitbakeTerminal, runBitbakeTerminalCustomCommand } from './BitbakeTerminal' import { type BitbakeDriver } from '../driver/BitbakeDriver' import { sanitizeForShell } from '../lib/src/BitbakeSettings' import { type BitbakeTaskDefinition, type BitbakeTaskProvider } from './BitbakeTaskProvider' +import { type LayerInfo } from '../lib/src/types/BitbakeScanResult' +import { DevtoolWorkspaceTreeItem } from './DevtoolWorkspacesView' let parsingPending = false @@ -41,6 +43,13 @@ export function registerBitbakeCommands (context: vscode.ExtensionContext, bitba })) } +export function registerDevtoolCommands (context: vscode.ExtensionContext, bitbakeWorkspace: BitbakeWorkspace, bitbakeDriver: BitbakeDriver): void { + context.subscriptions.push(vscode.commands.registerCommand('bitbake.devtool-modify', async (uri) => { await devtoolModifyCommand(bitbakeWorkspace, bitbakeDriver, uri) })) + context.subscriptions.push(vscode.commands.registerCommand('bitbake.devtool-update', async (uri) => { await devtoolUpdateCommand(bitbakeWorkspace, bitbakeDriver, uri) })) + context.subscriptions.push(vscode.commands.registerCommand('bitbake.devtool-reset', async (uri) => { await devtoolResetCommand(bitbakeWorkspace, bitbakeDriver, uri) })) + context.subscriptions.push(vscode.commands.registerCommand('bitbake.devtool-open-workspace', async (uri) => { await devtoolOpenWorkspaceCommand(bitbakeWorkspace, bitBakeProjectScanner, uri) })) +} + async function parseAllrecipes (bitbakeWorkspace: BitbakeWorkspace, taskProvider: BitbakeTaskProvider): Promise { logger.debug('Command: parse-recipes') @@ -130,6 +139,9 @@ async function selectRecipe (bitbakeWorkspace: BitbakeWorkspace, uri?: any, canC if (uri instanceof BitbakeRecipeTreeItem) { return uri.label } + if (uri instanceof DevtoolWorkspaceTreeItem) { + return uri.label as string + } // A vscode.Uri is provided when the command is called through the context menu of a .bb file if (uri !== undefined) { const extension = path.extname(uri.fsPath) @@ -192,3 +204,78 @@ async function rescanProject (bitbakeProjectScanner: BitBakeProjectScanner): Pro await bitbakeProjectScanner.rescanProject() } + +async function devtoolModifyCommand (bitbakeWorkspace: BitbakeWorkspace, bitbakeDriver: BitbakeDriver, uri?: any): Promise { + const chosenRecipe = await selectRecipe(bitbakeWorkspace, uri) + if (chosenRecipe !== undefined) { + logger.debug(`Command: devtool-modify: ${chosenRecipe}`) + const command = `devtool modify ${chosenRecipe}` + const process = await runBitbakeTerminalCustomCommand(bitbakeDriver, command, `Bitbake: Devtool Modify: ${chosenRecipe}`) + process.on('exit', (code) => { + if (code === 0) { + void bitBakeProjectScanner.rescanProject() + } + }) + } +} + +async function pickLayer (extraOption: string): Promise { + const layers = bitBakeProjectScanner.scanResult._layers + const chosenLayer = await vscode.window.showQuickPick([...layers.map(layer => layer.name), extraOption], { placeHolder: 'Choose target BitBake layer' }) + if (chosenLayer === undefined) { return } + + if (chosenLayer === extraOption) { + return { name: extraOption, path: '', priority: 0 } + } else { + return layers.find(layer => layer.name === chosenLayer) + } +} + +async function devtoolUpdateCommand (bitbakeWorkspace: BitbakeWorkspace, bitbakeDriver: BitbakeDriver, uri?: any): Promise { + const chosenRecipe = await selectRecipe(bitbakeWorkspace, uri) + if (chosenRecipe === undefined) { return } + const chosenLayer = await pickLayer('Original recipe\'s layer') + let command = '' + + if (chosenLayer?.name === 'Original recipe\'s layer') { + command = `devtool update-recipe ${chosenRecipe}` + } else { + command = `devtool update-recipe ${chosenRecipe} --append ${chosenLayer?.path}` + } + + logger.debug(`Command: devtool-update: ${chosenRecipe}`) + await runBitbakeTerminalCustomCommand(bitbakeDriver, command, `Bitbake: Devtool Update: ${chosenRecipe}`) +} + +async function devtoolResetCommand (bitbakeWorkspace: BitbakeWorkspace, bitbakeDriver: BitbakeDriver, uri?: any): Promise { + const chosenRecipe = await selectRecipe(bitbakeWorkspace, uri) + if (chosenRecipe !== undefined) { + logger.debug(`Command: devtool-reset: ${chosenRecipe}`) + const command = `devtool reset ${chosenRecipe}` + const process = await runBitbakeTerminalCustomCommand(bitbakeDriver, command, `Bitbake: Devtool Reset: ${chosenRecipe}`) + process.on('exit', (code) => { + if (code === 0) { + void bitBakeProjectScanner.rescanProject() + } + }) + } +} + +async function devtoolOpenWorkspaceCommand (bitbakeWorkspace: BitbakeWorkspace, bitbakeProjectScanner: BitBakeProjectScanner, uri?: any): Promise { + const chosenRecipe = await selectRecipe(bitbakeWorkspace, uri) + if (chosenRecipe === undefined) { return } + if (bitbakeProjectScanner.bitbakeDriver === undefined) { throw new Error('bitbakeDriver is undefined') } + + if (bitbakeProjectScanner.scanResult._workspaces.find((workspace) => workspace.name === chosenRecipe) === undefined) { + await devtoolModifyCommand(bitbakeWorkspace, bitbakeProjectScanner.bitbakeDriver, chosenRecipe) + } + + logger.debug(`Command: devtool-open-workspace: ${chosenRecipe}`) + const workspacePath = bitbakeProjectScanner.scanResult._workspaces.find((workspace) => workspace.name === chosenRecipe)?.path + if (workspacePath === undefined) { + logger.error('Devtool workspace not found') + return + } + // TODO convert URL outside of container. good luck! + await vscode.commands.executeCommand('vscode.openFolder', vscode.Uri.file(workspacePath), { forceNewWindow: true }) +} From 0d39171280a9bf9f5fa981cce322acb172946cd9 Mon Sep 17 00:00:00 2001 From: Enguerrand de Ribaucourt Date: Fri, 15 Dec 2023 09:25:12 +0100 Subject: [PATCH 5/9] Feature: Open bbappend file after devtool update-recipe --- client/src/ui/BitbakeCommands.ts | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/client/src/ui/BitbakeCommands.ts b/client/src/ui/BitbakeCommands.ts index 43c7bccc..8776278b 100644 --- a/client/src/ui/BitbakeCommands.ts +++ b/client/src/ui/BitbakeCommands.ts @@ -19,6 +19,8 @@ import { sanitizeForShell } from '../lib/src/BitbakeSettings' import { type BitbakeTaskDefinition, type BitbakeTaskProvider } from './BitbakeTaskProvider' import { type LayerInfo } from '../lib/src/types/BitbakeScanResult' import { DevtoolWorkspaceTreeItem } from './DevtoolWorkspacesView' +import { finishProcessExecution } from '../lib/src/utils/ProcessUtils' +import { type SpawnSyncReturns } from 'child_process' let parsingPending = false @@ -232,19 +234,40 @@ async function pickLayer (extraOption: string): Promise { } async function devtoolUpdateCommand (bitbakeWorkspace: BitbakeWorkspace, bitbakeDriver: BitbakeDriver, uri?: any): Promise { + const originalRecipeChoice = 'Update the original recipe' const chosenRecipe = await selectRecipe(bitbakeWorkspace, uri) if (chosenRecipe === undefined) { return } - const chosenLayer = await pickLayer('Original recipe\'s layer') + const chosenLayer = await pickLayer(originalRecipeChoice) let command = '' - if (chosenLayer?.name === 'Original recipe\'s layer') { + if (chosenLayer?.name === originalRecipeChoice) { command = `devtool update-recipe ${chosenRecipe}` } else { command = `devtool update-recipe ${chosenRecipe} --append ${chosenLayer?.path}` } logger.debug(`Command: devtool-update: ${chosenRecipe}`) - await runBitbakeTerminalCustomCommand(bitbakeDriver, command, `Bitbake: Devtool Update: ${chosenRecipe}`) + const process = runBitbakeTerminalCustomCommand(bitbakeDriver, command, `Bitbake: Devtool Update: ${chosenRecipe}`) + const res = await finishProcessExecution(process) + if (res.status === 0 && chosenLayer?.name !== originalRecipeChoice) { + await openDevtoolUpdateBBAppend(res, bitBakeProjectScanner) + void bitBakeProjectScanner.rescanProject() + } +} + +async function openDevtoolUpdateBBAppend (res: SpawnSyncReturns, bitbakeProjectScanner: BitBakeProjectScanner): Promise { + const output = res.stdout.toString() + // Regex to extract path from: NOTE: Writing append file .../meta-poky/recipes-core/busybox/busybox_1.36.1.bbappend + const regex = /NOTE: Writing append file (.*)/g + const match = regex.exec(output) + if (match === null) { + logger.error('Could not find bbappend file') + return + } + const bbappendPath = match[1] + const bbappendUri = vscode.Uri.file(bbappendPath) + logger.debug(`Opening devtool-update-recipe bbappend file: ${bbappendPath}`) + await vscode.commands.executeCommand('vscode.open', bbappendUri) } async function devtoolResetCommand (bitbakeWorkspace: BitbakeWorkspace, bitbakeDriver: BitbakeDriver, uri?: any): Promise { From 2a395798b61156b42e2b87693cc4aba8950af517 Mon Sep 17 00:00:00 2001 From: Enguerrand de Ribaucourt Date: Fri, 15 Dec 2023 10:06:05 +0100 Subject: [PATCH 6/9] Refactor: Remove bitBakeProjectScanner singleton There is no necessity for a singleton here. It makes the code harder to test. --- .../unit-tests/driver/scanner.test.ts | 6 ++- .../ui/bitbake-recipes-view.test.ts | 4 +- client/src/driver/BitBakeProjectScanner.ts | 8 ++-- client/src/extension.ts | 6 +-- client/src/ui/BitbakeCommands.ts | 48 +++++++++---------- client/src/ui/DevtoolWorkspacesView.ts | 3 -- 6 files changed, 37 insertions(+), 38 deletions(-) diff --git a/client/src/__tests__/unit-tests/driver/scanner.test.ts b/client/src/__tests__/unit-tests/driver/scanner.test.ts index 8644ebfb..c6385190 100644 --- a/client/src/__tests__/unit-tests/driver/scanner.test.ts +++ b/client/src/__tests__/unit-tests/driver/scanner.test.ts @@ -4,9 +4,11 @@ * ------------------------------------------------------------------------------------------ */ import path from 'path' -import { bitBakeProjectScanner } from '../../../driver/BitBakeProjectScanner' +import { BitBakeProjectScanner } from '../../../driver/BitBakeProjectScanner' import { BitbakeDriver } from '../../../driver/BitbakeDriver' +let bitBakeProjectScanner: BitBakeProjectScanner + const pathToBitbakeFolder = path.join(__dirname, '../../../../../integration-tests/project-folder/sources/poky/bitbake') const pathToBuildFolder = path.join(__dirname, '../../../../../integration-tests/project-folder/build') const pathToEnvScript = path.join(__dirname, '../../../../../integration-tests/project-folder/sources/poky/oe-init-build-env') @@ -25,7 +27,7 @@ describe('BitBakeProjectScanner', () => { }, workspaceFolder ) - bitBakeProjectScanner.setDriver(bitbakeDriver) + bitBakeProjectScanner = new BitBakeProjectScanner(bitbakeDriver) bitBakeProjectScanner.onChange.on(('scanReady'), () => { DoneCallback() }) diff --git a/client/src/__tests__/unit-tests/ui/bitbake-recipes-view.test.ts b/client/src/__tests__/unit-tests/ui/bitbake-recipes-view.test.ts index d1f90983..0de6a44e 100644 --- a/client/src/__tests__/unit-tests/ui/bitbake-recipes-view.test.ts +++ b/client/src/__tests__/unit-tests/ui/bitbake-recipes-view.test.ts @@ -6,14 +6,16 @@ import * as vscode from 'vscode' import { type BitbakeRecipeTreeItem, BitbakeRecipesView } from '../../../ui/BitbakeRecipesView' import { BitbakeWorkspace } from '../../../ui/BitbakeWorkspace' -import { bitBakeProjectScanner } from '../../../driver/BitBakeProjectScanner' +import { BitBakeProjectScanner } from '../../../driver/BitBakeProjectScanner' import { type BitbakeScanResult } from '../../../lib/src/types/BitbakeScanResult' +import { BitbakeDriver } from '../../../driver/BitbakeDriver' jest.mock('vscode') describe('BitbakeDriver Recipes View', () => { it('should list recipes', (done) => { const bitbakeWorkspace = new BitbakeWorkspace() + const bitBakeProjectScanner = new BitBakeProjectScanner(new BitbakeDriver()) bitbakeWorkspace.addActiveRecipe('base-files') const contextMock: vscode.ExtensionContext = { diff --git a/client/src/driver/BitBakeProjectScanner.ts b/client/src/driver/BitBakeProjectScanner.ts index f8ff4c48..6729f163 100644 --- a/client/src/driver/BitBakeProjectScanner.ts +++ b/client/src/driver/BitBakeProjectScanner.ts @@ -41,14 +41,14 @@ export class BitBakeProjectScanner { private readonly _bitbakeScanResult: BitbakeScanResult = { _classes: [], _includes: [], _layers: [], _overrides: [], _recipes: [], _workspaces: [] } private _shouldDeepExamine: boolean = false - private _bitbakeDriver: BitbakeDriver | undefined + private readonly _bitbakeDriver: BitbakeDriver private _languageClient: LanguageClient | undefined /// These attributes map bind mounts of the workDir to the host system if a docker container commandWrapper is used (-v). private containerMountPoint: string | undefined private hostMountPoint: string | undefined - setDriver (bitbakeDriver: BitbakeDriver): void { + constructor (bitbakeDriver: BitbakeDriver) { this._bitbakeDriver = bitbakeDriver } @@ -73,7 +73,7 @@ export class BitBakeProjectScanner { this._shouldDeepExamine = shouldDeepExamine } - get bitbakeDriver (): BitbakeDriver | undefined { + get bitbakeDriver (): BitbakeDriver { return this._bitbakeDriver } @@ -424,8 +424,6 @@ export class BitBakeProjectScanner { } } -export const bitBakeProjectScanner = new BitBakeProjectScanner() - function bbappendVersionMatches (bbappendVersion: string | undefined, recipeVersion: string | undefined): boolean { if (bbappendVersion === undefined) { return true diff --git a/client/src/extension.ts b/client/src/extension.ts index e79c83fa..a0d5dfd5 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -15,7 +15,7 @@ import { registerBitbakeCommands, registerDevtoolCommands } from './ui/BitbakeCo import { BitbakeWorkspace } from './ui/BitbakeWorkspace' import { BitbakeRecipesView } from './ui/BitbakeRecipesView' import { BitbakeStatusBar } from './ui/BitbakeStatusBar' -import { bitBakeProjectScanner } from './driver/BitBakeProjectScanner' +import { BitBakeProjectScanner } from './driver/BitBakeProjectScanner' import { BitbakeDocumentLinkProvider } from './documentLinkProvider' import { DevtoolWorkspacesView } from './ui/DevtoolWorkspacesView' @@ -60,7 +60,7 @@ export async function activate (context: vscode.ExtensionContext): Promise bitbakeExtensionContext = context logger.debug('Loaded bitbake workspace settings: ' + JSON.stringify(vscode.workspace.getConfiguration('bitbake'))) bitbakeDriver.loadSettings(vscode.workspace.getConfiguration('bitbake'), vscode.workspace.workspaceFolders?.[0].uri.fsPath) - bitBakeProjectScanner.setDriver(bitbakeDriver) + const bitBakeProjectScanner: BitBakeProjectScanner = new BitBakeProjectScanner(bitbakeDriver) updatePythonPath() bitbakeWorkspace.loadBitbakeWorkspace(context.workspaceState) bitbakeTaskProvider = new BitbakeTaskProvider(bitbakeDriver) @@ -104,7 +104,7 @@ export async function activate (context: vscode.ExtensionContext): Promise })) registerBitbakeCommands(context, bitbakeWorkspace, bitbakeTaskProvider, bitBakeProjectScanner) - registerDevtoolCommands(context, bitbakeWorkspace, bitbakeDriver) + registerDevtoolCommands(context, bitbakeWorkspace, bitBakeProjectScanner) logger.info('Congratulations, your extension "BitBake" is now active!') diff --git a/client/src/ui/BitbakeCommands.ts b/client/src/ui/BitbakeCommands.ts index 8776278b..2fade10c 100644 --- a/client/src/ui/BitbakeCommands.ts +++ b/client/src/ui/BitbakeCommands.ts @@ -11,7 +11,7 @@ import { logger } from '../lib/src/utils/OutputLogger' import { type BitbakeWorkspace } from './BitbakeWorkspace' import path from 'path' import { BitbakeRecipeTreeItem } from './BitbakeRecipesView' -import { type BitBakeProjectScanner, bitBakeProjectScanner } from '../driver/BitBakeProjectScanner' +import { type BitBakeProjectScanner } from '../driver/BitBakeProjectScanner' import { extractRecipeName } from '../lib/src/utils/files' import { runBitbakeTerminal, runBitbakeTerminalCustomCommand } from './BitbakeTerminal' import { type BitbakeDriver } from '../driver/BitbakeDriver' @@ -24,14 +24,14 @@ import { type SpawnSyncReturns } from 'child_process' let parsingPending = false -export function registerBitbakeCommands (context: vscode.ExtensionContext, bitbakeWorkspace: BitbakeWorkspace, bitbakeTaskProvider: BitbakeTaskProvider, bitbakeProjectScanner: BitBakeProjectScanner): void { +export function registerBitbakeCommands (context: vscode.ExtensionContext, bitbakeWorkspace: BitbakeWorkspace, bitbakeTaskProvider: BitbakeTaskProvider, bitBakeProjectScanner: BitBakeProjectScanner): void { context.subscriptions.push(vscode.commands.registerCommand('bitbake.parse-recipes', async () => { await parseAllrecipes(bitbakeWorkspace, bitbakeTaskProvider) })) context.subscriptions.push(vscode.commands.registerCommand('bitbake.build-recipe', async (uri) => { await buildRecipeCommand(bitbakeWorkspace, bitbakeTaskProvider.bitbakeDriver, uri) })) context.subscriptions.push(vscode.commands.registerCommand('bitbake.clean-recipe', async (uri) => { await cleanRecipeCommand(bitbakeWorkspace, bitbakeTaskProvider.bitbakeDriver, uri) })) context.subscriptions.push(vscode.commands.registerCommand('bitbake.run-task', async (uri, task) => { await runTaskCommand(bitbakeWorkspace, bitbakeTaskProvider.bitbakeDriver, uri, task) })) context.subscriptions.push(vscode.commands.registerCommand('bitbake.drop-recipe', async (uri) => { await dropRecipe(bitbakeWorkspace, uri) })) context.subscriptions.push(vscode.commands.registerCommand('bitbake.watch-recipe', async (recipe) => { await addActiveRecipe(bitbakeWorkspace, recipe) })) - context.subscriptions.push(vscode.commands.registerCommand('bitbake.rescan-project', async () => { await rescanProject(bitbakeProjectScanner) })) + context.subscriptions.push(vscode.commands.registerCommand('bitbake.rescan-project', async () => { await rescanProject(bitBakeProjectScanner) })) // Handles enqueued parsing requests (onSave) context.subscriptions.push( @@ -45,10 +45,10 @@ export function registerBitbakeCommands (context: vscode.ExtensionContext, bitba })) } -export function registerDevtoolCommands (context: vscode.ExtensionContext, bitbakeWorkspace: BitbakeWorkspace, bitbakeDriver: BitbakeDriver): void { - context.subscriptions.push(vscode.commands.registerCommand('bitbake.devtool-modify', async (uri) => { await devtoolModifyCommand(bitbakeWorkspace, bitbakeDriver, uri) })) - context.subscriptions.push(vscode.commands.registerCommand('bitbake.devtool-update', async (uri) => { await devtoolUpdateCommand(bitbakeWorkspace, bitbakeDriver, uri) })) - context.subscriptions.push(vscode.commands.registerCommand('bitbake.devtool-reset', async (uri) => { await devtoolResetCommand(bitbakeWorkspace, bitbakeDriver, uri) })) +export function registerDevtoolCommands (context: vscode.ExtensionContext, bitbakeWorkspace: BitbakeWorkspace, bitBakeProjectScanner: BitBakeProjectScanner): void { + context.subscriptions.push(vscode.commands.registerCommand('bitbake.devtool-modify', async (uri) => { await devtoolModifyCommand(bitbakeWorkspace, bitBakeProjectScanner, uri) })) + context.subscriptions.push(vscode.commands.registerCommand('bitbake.devtool-update', async (uri) => { await devtoolUpdateCommand(bitbakeWorkspace, bitBakeProjectScanner, uri) })) + context.subscriptions.push(vscode.commands.registerCommand('bitbake.devtool-reset', async (uri) => { await devtoolResetCommand(bitbakeWorkspace, bitBakeProjectScanner, uri) })) context.subscriptions.push(vscode.commands.registerCommand('bitbake.devtool-open-workspace', async (uri) => { await devtoolOpenWorkspaceCommand(bitbakeWorkspace, bitBakeProjectScanner, uri) })) } @@ -198,21 +198,21 @@ async function runBitbakeTask (task: vscode.Task, taskProvider: vscode.TaskProvi } } -async function rescanProject (bitbakeProjectScanner: BitBakeProjectScanner): Promise { - if (await bitbakeProjectScanner.bitbakeDriver?.checkBitbakeSettingsSanity() !== true) { +async function rescanProject (bitBakeProjectScanner: BitBakeProjectScanner): Promise { + if (!(await bitBakeProjectScanner.bitbakeDriver?.checkBitbakeSettingsSanity())) { logger.warn('bitbake settings are not sane, skip rescan') return } - await bitbakeProjectScanner.rescanProject() + await bitBakeProjectScanner.rescanProject() } -async function devtoolModifyCommand (bitbakeWorkspace: BitbakeWorkspace, bitbakeDriver: BitbakeDriver, uri?: any): Promise { +async function devtoolModifyCommand (bitbakeWorkspace: BitbakeWorkspace, bitBakeProjectScanner: BitBakeProjectScanner, uri?: any): Promise { const chosenRecipe = await selectRecipe(bitbakeWorkspace, uri) if (chosenRecipe !== undefined) { logger.debug(`Command: devtool-modify: ${chosenRecipe}`) const command = `devtool modify ${chosenRecipe}` - const process = await runBitbakeTerminalCustomCommand(bitbakeDriver, command, `Bitbake: Devtool Modify: ${chosenRecipe}`) + const process = await runBitbakeTerminalCustomCommand(bitBakeProjectScanner.bitbakeDriver, command, `Bitbake: Devtool Modify: ${chosenRecipe}`) process.on('exit', (code) => { if (code === 0) { void bitBakeProjectScanner.rescanProject() @@ -221,7 +221,7 @@ async function devtoolModifyCommand (bitbakeWorkspace: BitbakeWorkspace, bitbake } } -async function pickLayer (extraOption: string): Promise { +async function pickLayer (extraOption: string, bitBakeProjectScanner: BitBakeProjectScanner): Promise { const layers = bitBakeProjectScanner.scanResult._layers const chosenLayer = await vscode.window.showQuickPick([...layers.map(layer => layer.name), extraOption], { placeHolder: 'Choose target BitBake layer' }) if (chosenLayer === undefined) { return } @@ -233,11 +233,11 @@ async function pickLayer (extraOption: string): Promise { } } -async function devtoolUpdateCommand (bitbakeWorkspace: BitbakeWorkspace, bitbakeDriver: BitbakeDriver, uri?: any): Promise { +async function devtoolUpdateCommand (bitbakeWorkspace: BitbakeWorkspace, bitBakeProjectScanner: BitBakeProjectScanner, uri?: any): Promise { const originalRecipeChoice = 'Update the original recipe' const chosenRecipe = await selectRecipe(bitbakeWorkspace, uri) if (chosenRecipe === undefined) { return } - const chosenLayer = await pickLayer(originalRecipeChoice) + const chosenLayer = await pickLayer(originalRecipeChoice, bitBakeProjectScanner) let command = '' if (chosenLayer?.name === originalRecipeChoice) { @@ -247,7 +247,7 @@ async function devtoolUpdateCommand (bitbakeWorkspace: BitbakeWorkspace, bitbake } logger.debug(`Command: devtool-update: ${chosenRecipe}`) - const process = runBitbakeTerminalCustomCommand(bitbakeDriver, command, `Bitbake: Devtool Update: ${chosenRecipe}`) + const process = runBitbakeTerminalCustomCommand(bitBakeProjectScanner.bitbakeDriver, command, `Bitbake: Devtool Update: ${chosenRecipe}`) const res = await finishProcessExecution(process) if (res.status === 0 && chosenLayer?.name !== originalRecipeChoice) { await openDevtoolUpdateBBAppend(res, bitBakeProjectScanner) @@ -255,7 +255,7 @@ async function devtoolUpdateCommand (bitbakeWorkspace: BitbakeWorkspace, bitbake } } -async function openDevtoolUpdateBBAppend (res: SpawnSyncReturns, bitbakeProjectScanner: BitBakeProjectScanner): Promise { +async function openDevtoolUpdateBBAppend (res: SpawnSyncReturns, bitBakeProjectScanner: BitBakeProjectScanner): Promise { const output = res.stdout.toString() // Regex to extract path from: NOTE: Writing append file .../meta-poky/recipes-core/busybox/busybox_1.36.1.bbappend const regex = /NOTE: Writing append file (.*)/g @@ -270,12 +270,12 @@ async function openDevtoolUpdateBBAppend (res: SpawnSyncReturns, bitbake await vscode.commands.executeCommand('vscode.open', bbappendUri) } -async function devtoolResetCommand (bitbakeWorkspace: BitbakeWorkspace, bitbakeDriver: BitbakeDriver, uri?: any): Promise { +async function devtoolResetCommand (bitbakeWorkspace: BitbakeWorkspace, bitBakeProjectScanner: BitBakeProjectScanner, uri?: any): Promise { const chosenRecipe = await selectRecipe(bitbakeWorkspace, uri) if (chosenRecipe !== undefined) { logger.debug(`Command: devtool-reset: ${chosenRecipe}`) const command = `devtool reset ${chosenRecipe}` - const process = await runBitbakeTerminalCustomCommand(bitbakeDriver, command, `Bitbake: Devtool Reset: ${chosenRecipe}`) + const process = await runBitbakeTerminalCustomCommand(bitBakeProjectScanner.bitbakeDriver, command, `Bitbake: Devtool Reset: ${chosenRecipe}`) process.on('exit', (code) => { if (code === 0) { void bitBakeProjectScanner.rescanProject() @@ -284,17 +284,17 @@ async function devtoolResetCommand (bitbakeWorkspace: BitbakeWorkspace, bitbakeD } } -async function devtoolOpenWorkspaceCommand (bitbakeWorkspace: BitbakeWorkspace, bitbakeProjectScanner: BitBakeProjectScanner, uri?: any): Promise { +async function devtoolOpenWorkspaceCommand (bitbakeWorkspace: BitbakeWorkspace, bitBakeProjectScanner: BitBakeProjectScanner, uri?: any): Promise { const chosenRecipe = await selectRecipe(bitbakeWorkspace, uri) if (chosenRecipe === undefined) { return } - if (bitbakeProjectScanner.bitbakeDriver === undefined) { throw new Error('bitbakeDriver is undefined') } + if (bitBakeProjectScanner.bitbakeDriver === undefined) { throw new Error('bitbakeDriver is undefined') } - if (bitbakeProjectScanner.scanResult._workspaces.find((workspace) => workspace.name === chosenRecipe) === undefined) { - await devtoolModifyCommand(bitbakeWorkspace, bitbakeProjectScanner.bitbakeDriver, chosenRecipe) + if (bitBakeProjectScanner.scanResult._workspaces.find((workspace) => workspace.name === chosenRecipe) === undefined) { + await devtoolModifyCommand(bitbakeWorkspace, bitBakeProjectScanner, chosenRecipe) } logger.debug(`Command: devtool-open-workspace: ${chosenRecipe}`) - const workspacePath = bitbakeProjectScanner.scanResult._workspaces.find((workspace) => workspace.name === chosenRecipe)?.path + const workspacePath = bitBakeProjectScanner.scanResult._workspaces.find((workspace) => workspace.name === chosenRecipe)?.path if (workspacePath === undefined) { logger.error('Devtool workspace not found') return diff --git a/client/src/ui/DevtoolWorkspacesView.ts b/client/src/ui/DevtoolWorkspacesView.ts index 8f331d65..8f757298 100644 --- a/client/src/ui/DevtoolWorkspacesView.ts +++ b/client/src/ui/DevtoolWorkspacesView.ts @@ -25,12 +25,9 @@ export class DevtoolWorkspacesView { class DevtoolTreeDataProvider implements vscode.TreeDataProvider { private readonly _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter() readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event - private readonly bitbakeProjectScanner: BitBakeProjectScanner private bitbakeScanResults: BitbakeScanResult = { _layers: [], _classes: [], _includes: [], _recipes: [], _overrides: [], _workspaces: [] } constructor (bitbakeProjectScanner: BitBakeProjectScanner) { - this.bitbakeProjectScanner = bitbakeProjectScanner - bitbakeProjectScanner.onChange.on('scanReady', (scanResults: BitbakeScanResult) => { // In case a parsing error was just introduced, we keep the previous results to keep navigation functional if (this.bitbakeScanResults._recipes.length === 0 || scanResults._recipes.length > 0) { From 7f17a673d78d73adcc0a40641f9ec34beab09a28 Mon Sep 17 00:00:00 2001 From: Enguerrand de Ribaucourt Date: Fri, 15 Dec 2023 10:06:40 +0100 Subject: [PATCH 7/9] Test: Add unit test for devtool workspaces view --- .../ui/devtool-workspaces-view.test.ts | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 client/src/__tests__/unit-tests/ui/devtool-workspaces-view.test.ts diff --git a/client/src/__tests__/unit-tests/ui/devtool-workspaces-view.test.ts b/client/src/__tests__/unit-tests/ui/devtool-workspaces-view.test.ts new file mode 100644 index 00000000..dfc8ebc8 --- /dev/null +++ b/client/src/__tests__/unit-tests/ui/devtool-workspaces-view.test.ts @@ -0,0 +1,53 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) 2023 Savoir-faire Linux. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + * ------------------------------------------------------------------------------------------ */ + +import * as vscode from 'vscode' +import { type DevtoolWorkspaceTreeItem, DevtoolWorkspacesView } from '../../../ui/DevtoolWorkspacesView' +import { BitBakeProjectScanner } from '../../../driver/BitBakeProjectScanner' +import { type BitbakeScanResult } from '../../../lib/src/types/BitbakeScanResult' +import { BitbakeDriver } from '../../../driver/BitbakeDriver' + +jest.mock('vscode') + +describe('Devtool Worskapces View', () => { + it('should list devtool workspaces', (done) => { + const contextMock: vscode.ExtensionContext = { + subscriptions: { + push: jest.fn() + } + } as any + + const scanResult: BitbakeScanResult = { + _recipes: [], + _includes: [], + _layers: [], + _classes: [], + _overrides: [], + _workspaces: [ + { + name: 'dropbear', + path: '/build/workspace/dropbear' + } + ] + } + + const bitBakeProjectScanner = new BitBakeProjectScanner(new BitbakeDriver()) + + vscode.window.registerTreeDataProvider = jest.fn().mockImplementation( + async (viewId: string, treeDataProvider: vscode.TreeDataProvider): Promise => { + const rootTreeItem = await treeDataProvider.getChildren(undefined) + expect(rootTreeItem).toBeDefined() + expect(rootTreeItem?.length).toStrictEqual(2) + const recipeItem = (rootTreeItem as DevtoolWorkspaceTreeItem[])[0] + expect(recipeItem.workspace.name).toStrictEqual('dropbear') + + done() + }) + + const devtoolWorkspacesView = new DevtoolWorkspacesView(bitBakeProjectScanner) + bitBakeProjectScanner.onChange.emit('scanReady', scanResult) + devtoolWorkspacesView.registerView(contextMock) + }) +}) From b7650182aea119755570abf194cda97515e67550 Mon Sep 17 00:00:00 2001 From: Enguerrand de Ribaucourt Date: Fri, 15 Dec 2023 10:26:17 +0100 Subject: [PATCH 8/9] Test: Bitbake devtool-modify command --- integration-tests/src/tests/bitbake-commands.test.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/integration-tests/src/tests/bitbake-commands.test.ts b/integration-tests/src/tests/bitbake-commands.test.ts index f4a95af0..6fe2085b 100644 --- a/integration-tests/src/tests/bitbake-commands.test.ts +++ b/integration-tests/src/tests/bitbake-commands.test.ts @@ -69,4 +69,12 @@ suite('Bitbake Commands Test Suite', () => { const files = await vscode.workspace.findFiles('build/tmp/work/*/base-files/*/temp/log.do_fetch') assert.strictEqual(files.length, 1) }).timeout(300000) + + test('Bitbake can create a devtool modify workspace', async () => { + await vscode.commands.executeCommand('bitbake.devtool-modify', 'busybox') + await assertWillComeTrue(async () => { + const files = await vscode.workspace.findFiles('build/workspace/sources/busybox/README') + return files.length === 1 + }) + }) }) From ac1081c917a98fd000e22a6c5b6b075c580df8d4 Mon Sep 17 00:00:00 2001 From: Enguerrand de Ribaucourt Date: Fri, 15 Dec 2023 10:27:16 +0100 Subject: [PATCH 9/9] Test: Devtool workspace scanning --- .../unit-tests/driver/scanner.test.ts | 32 ++++++++++++++++++- .../src/tests/bitbake-commands.test.ts | 2 +- .../src/tests/bitbake-parse.test.ts | 2 +- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/client/src/__tests__/unit-tests/driver/scanner.test.ts b/client/src/__tests__/unit-tests/driver/scanner.test.ts index c6385190..1028c60c 100644 --- a/client/src/__tests__/unit-tests/driver/scanner.test.ts +++ b/client/src/__tests__/unit-tests/driver/scanner.test.ts @@ -31,7 +31,23 @@ describe('BitBakeProjectScanner', () => { bitBakeProjectScanner.onChange.on(('scanReady'), () => { DoneCallback() }) - void bitBakeProjectScanner.rescanProject() + bitBakeProjectScanner.bitbakeDriver.spawnBitbakeProcess('devtool modify busybox').then((child) => { + child.on('close', () => { + void bitBakeProjectScanner.rescanProject() + }) + }, (error) => { + throw error + }) + }, 300000) + + afterAll((done) => { + bitBakeProjectScanner.bitbakeDriver.spawnBitbakeProcess('devtool reset busybox').then((child) => { + child.on('close', () => { + done() + }) + }, (error) => { + throw error + }) }, 300000) it('can get a list of layers', async () => { @@ -86,4 +102,18 @@ describe('BitBakeProjectScanner', () => { ]) ) }) + + it('can get a list of devtool workspaces', async () => { + const devtoolWorkspaces = bitBakeProjectScanner.scanResult._workspaces + expect(devtoolWorkspaces.length).toBeGreaterThan(0) + expect(devtoolWorkspaces).toEqual( + expect.arrayContaining([ + expect.objectContaining( + { + name: 'busybox' + } + ) + ]) + ) + }) }) diff --git a/integration-tests/src/tests/bitbake-commands.test.ts b/integration-tests/src/tests/bitbake-commands.test.ts index 6fe2085b..4e09b990 100644 --- a/integration-tests/src/tests/bitbake-commands.test.ts +++ b/integration-tests/src/tests/bitbake-commands.test.ts @@ -76,5 +76,5 @@ suite('Bitbake Commands Test Suite', () => { const files = await vscode.workspace.findFiles('build/workspace/sources/busybox/README') return files.length === 1 }) - }) + }).timeout(300000) }) diff --git a/integration-tests/src/tests/bitbake-parse.test.ts b/integration-tests/src/tests/bitbake-parse.test.ts index 1a2d225c..c291489b 100644 --- a/integration-tests/src/tests/bitbake-parse.test.ts +++ b/integration-tests/src/tests/bitbake-parse.test.ts @@ -17,7 +17,7 @@ suite('Bitbake Parsing Test Suite', () => { let buildFolder: vscode.Uri suiteSetup(async function (this: Mocha.Context) { - this.timeout(100000) + this.timeout(300000) await assertWorkspaceWillBeOpen() workspaceURI = (vscode.workspace.workspaceFolders as vscode.WorkspaceFolder[])[0].uri buildFolder = vscode.Uri.joinPath(workspaceURI, 'build')