Skip to content

Commit

Permalink
Feature: Implement a basic OE Depends Dot view
Browse files Browse the repository at this point in the history
  • Loading branch information
deribaucourt committed Jan 8, 2025
1 parent 8355fc5 commit 37c327f
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 93 deletions.
114 changes: 104 additions & 10 deletions client/src/ui/DependsDotView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,22 @@
import * as vscode from 'vscode'
import ejs from 'ejs'
import { BitbakeDriver } from '../driver/BitbakeDriver'
import { logger } from '../lib/src/utils/OutputLogger'
import { BitbakeTaskDefinition } from './BitbakeTaskProvider'
import { runBitbakeTerminal } from './BitbakeTerminal'
import { finishProcessExecution } from '../utils/ProcessUtils'

/*
TODO Beautify the view
- make div elements side by side
- Add some spacing
TODO Display a graph rather than text
TODO Make the graph interactive (click on elements open their .bb file) (bonus: right click brings the commands menu)
TODO Auto-refresh the dotfile when needed when click dependsDot
TODO Use the select recipe command to get the image recipe list (not for the packageName though?)
TODO Add tests for this feature
TODO Save field values on workspace reload
*/

export class DependsDotView {
private readonly provider: DependsDotViewProvider
Expand All @@ -27,6 +43,10 @@ class DependsDotViewProvider implements vscode.WebviewViewProvider {
private view?: vscode.WebviewView;
private extensionUri: vscode.Uri

private depType: string = "-w";
private graphRecipe: string = "";
private packageName: string = "";

constructor (bitbakeDriver: BitbakeDriver, extensionUri: vscode.Uri) {
this.bitbakeDriver = bitbakeDriver
this.extensionUri = extensionUri
Expand All @@ -46,25 +66,37 @@ class DependsDotViewProvider implements vscode.WebviewViewProvider {

webviewView.webview.html = await this.getHtmlForWebview(webviewView.webview);

webviewView.webview.onDidReceiveMessage(data => {
switch (data.type) {
case 'colorSelected':
{
vscode.window.activeTextEditor?.insertSnippet(new vscode.SnippetString(`#${data.value}`));
break;
}
}
});
webviewView.webview.onDidReceiveMessage(this.onWebviewMessage.bind(this));
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
private onWebviewMessage(data: any) : any {
switch (data.type) {
case 'depType':
this.depType = data.value === "depends" ? "-d" : "-w";
break;
case 'graphRecipe':
this.graphRecipe = data.value;
break;
case 'packageName':
this.packageName = data.value;
break;
case 'genDotFile':
this.genDotFile();
break;
case 'runOeDepends':
this.runOeDepends();
break;
}
}

private getHtmlForWebview(webview: vscode.Webview): Promise<string> {
const htmlUri = webview.asWebviewUri(vscode.Uri.joinPath(this.extensionUri, 'client', 'web', 'depends-dot', 'main.html'));
const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath(this.extensionUri, 'client', 'web', 'depends-dot', 'main.js'));
const styleResetUri = webview.asWebviewUri(vscode.Uri.joinPath(this.extensionUri, 'client', 'web', 'depends-dot', 'reset.css'));
const styleVSCodeUri = webview.asWebviewUri(vscode.Uri.joinPath(this.extensionUri, 'client', 'web', 'depends-dot', 'vscode.css'));
const styleMainUri = webview.asWebviewUri(vscode.Uri.joinPath(this.extensionUri, 'client', 'web', 'depends-dot', 'main.css'));

// Use a nonce to only allow a specific script to be run.
const nonce = this.getNonce();

const html = ejs.renderFile(htmlUri.fsPath, {
Expand All @@ -78,6 +110,7 @@ class DependsDotViewProvider implements vscode.WebviewViewProvider {
return html;
}

/// The Nonce is a random value used to validate the CSP policy
private getNonce() {
let text = '';
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
Expand All @@ -86,4 +119,65 @@ class DependsDotViewProvider implements vscode.WebviewViewProvider {
}
return text;
}

private async genDotFile() : Promise<void> {
if(this.graphRecipe === "") {
logger.error("genDotFile: No image recipe selected");
void vscode.window.showErrorMessage(`Please select an image recipe first`);
return;
}
logger.info(`genDotFile: ${this.graphRecipe}`)
// isTaskexpStarted = true
// TODO add blocker for runOeDependsDOt to wait for completion.
// TODO do not bring to foreground
const process = await runBitbakeTerminal(this.bitbakeDriver,
{
specialCommand: `bitbake -g ${this.graphRecipe}`,
} as BitbakeTaskDefinition,
`Bitbake: genDotFile: ${this.graphRecipe}`,)
process.onExit((e) => {
// isTaskexpStarted = false
if (e.exitCode !== 0) {
void vscode.window.showErrorMessage(`Failed to generate dependency graph with exit code ${e.exitCode}. See terminal output.`)
}
})
}

private async runOeDepends() : Promise<void> {
if(this.packageName === "") {
logger.error("genDotFile: No package selected");
void vscode.window.showErrorMessage(`Please select a package first`);
return;
}
logger.info(`runOeDepends: ${this.packageName}`);
// TODO do not bring to foreground
const process = runBitbakeTerminal(this.bitbakeDriver,
{
specialCommand: `oe-depends-dot -k ${this.packageName} ${this.depType} ./task-depends.dot`,
} as BitbakeTaskDefinition,
`Bitbake: oeDependsDot: ${this.packageName}`,)
const result = await finishProcessExecution(process)
if (result.status !== 0) {
void vscode.window.showErrorMessage(`Failed to run oe-depends-dot with exit code ${result.status}. See terminal output.`)
}
const filtered_output = this.filterOeDependsOutput(result.stdout.toString());
this.view?.webview.postMessage({ type: 'results', value: filtered_output });
}

/// Remove all lines of output that do not contain the actual results
private filterOeDependsOutput(output: string): string {
let filtered_output = ''
if(this.depType === "-d") {
filtered_output = output
.split('\n')
.filter(line => line.includes('Depends: '))
.join('\n');
} else {
filtered_output = output
.split('\n')
.filter(line => line.includes(' -> '))
.join('\n');
}
return filtered_output;
}
}
45 changes: 41 additions & 4 deletions client/web/depends-dot/main.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
<!-- --------------------------------------------------------------------------------------------
* Copyright (c) 2023 Savoir-faire Linux. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ -->

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">

Expand All @@ -16,13 +22,44 @@
<link href="<%= styleVSCodeUri %>" rel="stylesheet">
<link href="<%= styleMainUri %>" rel="stylesheet">

<title>Cat Colors</title>
<title>OE Depends Dot</title>
</head>

<body>
<ul class="color-list">
</ul>
<!-- To run oe-depends-dot, we need the following elements:
Name of the dotfile recipe field
bullet list to select among "why" or "depends" for the dependency - a button to run oe-depends-dot - a read-only text box to display the results
Name of the recipe to inspect the dependencies
Button to generate the dependency graph
-->
<div>
<h3>Recipe graph:</h3>
<input type="text" id="graphRecipe" />
<button id="genDotFile">Generate dependency dotfile</button>
</div>

<div>
<h3>Dependency type:</h3>
<ul id="depType">
<li><input type="radio" name="dependencyType" value="why" checked>Why</li>
<li><input type="radio" name="dependencyType" value="depends">Depends</li>
</ul>
</div>

<div>
<h3>Package:</h3>
<input type="text" id="packageName" />
</div>

<div>
<button id="runOeDepends">Run oe-depends-dot</button>
</div>

<button class="add-color-button">Add Color</button>
<div>
<h3>Results:</h3>
<textarea id="results" readonly></textarea>
</div>

<script nonce="<%= nonce %>" src="<%= scriptUri %>"></script>
</body>
Expand Down
103 changes: 26 additions & 77 deletions client/web/depends-dot/main.js
Original file line number Diff line number Diff line change
@@ -1,98 +1,47 @@
/** --------------------------------------------------------------------------------------------
* Copyright (c) 2023 Savoir-faire Linux. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
//@ts-check

// This script will be run within the webview itself
// It cannot access the main VS Code APIs directly.
(function () {

Check failure on line 9 in client/web/depends-dot/main.js

View workflow job for this annotation

GitHub Actions / build-test-vscode

incorrect header
const vscode = acquireVsCodeApi();

Check failure on line 10 in client/web/depends-dot/main.js

View workflow job for this annotation

GitHub Actions / build-test-vscode

'acquireVsCodeApi' is not defined
const oldState = vscode.getState() || { };

Check failure on line 11 in client/web/depends-dot/main.js

View workflow job for this annotation

GitHub Actions / build-test-vscode

'oldState' is assigned a value but never used

const oldState = vscode.getState() || { colors: [] };
// HTML Listeners
document.querySelector('#depType').addEventListener('click', () => {
vscode.postMessage({ type: 'depType', value: event.target.value });
});

/** @type {Array<{ value: string }>} */
let colors = oldState.colors;
document.querySelector('#graphRecipe').addEventListener('input', () => {
const value = document.querySelector('#graphRecipe').value;
vscode.postMessage({ type: 'graphRecipe', value });
});

updateColorList(colors);
document.querySelector('#packageName').addEventListener('input', () => {
const value = document.querySelector('#packageName').value;
vscode.postMessage({ type: 'packageName', value });
});

document.querySelector('#genDotFile').addEventListener('click', () => {
vscode.postMessage({ type: 'genDotFile' });
});

document.querySelector('.add-color-button').addEventListener('click', () => {
addColor();
document.querySelector('#runOeDepends').addEventListener('click', () => {
vscode.postMessage({ type: 'runOeDepends' });
});

// Handle messages sent from the extension to the webview
// Extension Listeners
window.addEventListener('message', event => {
const message = event.data; // The json data that the extension sent
switch (message.type) {
case 'addColor':
case 'results':
{
addColor();
document.querySelector('#results').textContent = message.value;
break;
}
case 'clearColors':
{
colors = [];
updateColorList(colors);
break;
}

}
});

/**
* @param {Array<{ value: string }>} colors
*/
function updateColorList(colors) {
const ul = document.querySelector('.color-list');
ul.textContent = '';
for (const color of colors) {
const li = document.createElement('li');
li.className = 'color-entry';

const colorPreview = document.createElement('div');
colorPreview.className = 'color-preview';
colorPreview.style.backgroundColor = `#${color.value}`;
colorPreview.addEventListener('click', () => {
onColorClicked(color.value);
});
li.appendChild(colorPreview);

const input = document.createElement('input');
input.className = 'color-input';
input.type = 'text';
input.value = color.value;
input.addEventListener('change', (e) => {
const value = e.target.value;
if (!value) {
// Treat empty value as delete
colors.splice(colors.indexOf(color), 1);
} else {
color.value = value;
}
updateColorList(colors);
});
li.appendChild(input);

ul.appendChild(li);
}

// Update the saved state
vscode.setState({ colors: colors });
}

/**
* @param {string} color
*/
function onColorClicked(color) {
vscode.postMessage({ type: 'colorSelected', value: color });
}

/**
* @returns string
*/
function getNewCalicoColor() {
const colors = ['020202', 'f1eeee', 'a85b20', 'daab70', 'efcb99'];
return colors[Math.floor(Math.random() * colors.length)];
}

function addColor() {
colors.push({ value: getNewCalicoColor() });
updateColorList(colors);
}
}());
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -563,8 +563,8 @@
{
"type": "webview",
"id": "bitbake.oeDependsDot",
"name": "Dependency Graph",
"contextualTitle": "Dependency graph",
"name": "Dependency Analyzer",
"contextualTitle": "Dependency Analyzer",
"icon": "$(graph)",
"when": "bitbake.active"
}
Expand Down

0 comments on commit 37c327f

Please sign in to comment.