Skip to content

Commit

Permalink
Feature: Present simple recipe dependency graphs
Browse files Browse the repository at this point in the history
The oe-depends-dot output is now no longer a simple text box.
Dynamic HTML content with clickable recipes is now generated.

We could in the future do more complex/merged graphs like the git-graph
extension does.
  • Loading branch information
deribaucourt committed Jan 10, 2025
1 parent 9974f41 commit ea78e75
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 16 deletions.
2 changes: 1 addition & 1 deletion client/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ export async function activate (context: vscode.ExtensionContext): Promise<void>
bitbakeRecipesView.registerView(context)
devtoolWorkspacesView = new DevtoolWorkspacesView(bitBakeProjectScanner)
devtoolWorkspacesView.registerView(context)
dependsDotWebview = new DependsDotView(bitbakeDriver, context.extensionUri)
dependsDotWebview = new DependsDotView(bitBakeProjectScanner, context.extensionUri)
dependsDotWebview.registerView(context)
void vscode.commands.executeCommand('setContext', 'bitbake.active', true)
const bitbakeStatusBar = new BitbakeStatusBar(bitBakeProjectScanner)
Expand Down
42 changes: 36 additions & 6 deletions client/src/ui/DependsDotView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import { logger } from '../lib/src/utils/OutputLogger'
import { BitbakeTaskDefinition } from './BitbakeTaskProvider'
import { runBitbakeTerminal } from './BitbakeTerminal'
import { finishProcessExecution } from '../utils/ProcessUtils'
import { BitBakeProjectScanner } from '../driver/BitBakeProjectScanner'
import { ElementInfo } from '../lib/src/types/BitbakeScanResult'
import path from 'path'

/*
TODO Beautify the view
Expand All @@ -22,14 +25,17 @@ import { finishProcessExecution } from '../utils/ProcessUtils'
TODO gray out the results when the graph or package is not up-to-date
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
TODO Save field values on workspace reload (https://code.visualstudio.com/api/extension-guides/webview#getstate-and-setstate and serializer)
TODO test styling in white mode and high-contrast mode
TODO sanitize text input (server side)
TODO add a gif in the README for this feature
*/

export class DependsDotView {
private readonly provider: DependsDotViewProvider

constructor (bitbakeDriver: BitbakeDriver, extensionUri: vscode.Uri) {
this.provider = new DependsDotViewProvider(bitbakeDriver, extensionUri)
constructor (bitbakeProjectScanner: BitBakeProjectScanner, extensionUri: vscode.Uri) {
this.provider = new DependsDotViewProvider(bitbakeProjectScanner, extensionUri)
}

registerView (context: vscode.ExtensionContext): void {
Expand All @@ -41,6 +47,7 @@ export class DependsDotView {

class DependsDotViewProvider implements vscode.WebviewViewProvider {
private readonly bitbakeDriver: BitbakeDriver
private readonly bitbakeProjectScanner: BitBakeProjectScanner
public static readonly viewType = "bitbake.oeDependsDot"
private view?: vscode.WebviewView;
private extensionUri: vscode.Uri
Expand All @@ -49,8 +56,9 @@ class DependsDotViewProvider implements vscode.WebviewViewProvider {
private graphRecipe: string = "";
private packageName: string = "";

constructor (bitbakeDriver: BitbakeDriver, extensionUri: vscode.Uri) {
this.bitbakeDriver = bitbakeDriver
constructor (bitbakeProjectScanner: BitBakeProjectScanner, extensionUri: vscode.Uri) {
this.bitbakeDriver = bitbakeProjectScanner.bitbakeDriver
this.bitbakeProjectScanner = bitbakeProjectScanner
this.extensionUri = extensionUri
}

Expand Down Expand Up @@ -89,6 +97,9 @@ class DependsDotViewProvider implements vscode.WebviewViewProvider {
case 'runOeDepends':
this.runOeDepends();
break;
case 'openRecipe':
this.openRecipe(data.value);
break;
}
}

Expand Down Expand Up @@ -163,7 +174,7 @@ class DependsDotViewProvider implements vscode.WebviewViewProvider {
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 });
this.view?.webview.postMessage({ type: 'results', value: filtered_output, depType: this.depType });
}

/// Remove all lines of output that do not contain the actual results
Expand All @@ -173,6 +184,7 @@ class DependsDotViewProvider implements vscode.WebviewViewProvider {
filtered_output = output
.split('\n')
.filter(line => line.includes('Depends: '))
.map(line => line.replace('Depends: ', ''))
.join('\n');
} else {
filtered_output = output
Expand All @@ -182,4 +194,22 @@ class DependsDotViewProvider implements vscode.WebviewViewProvider {
}
return filtered_output;
}

private openRecipe(recipeName: string) {
recipeName = recipeName.replace(/\r/g, '');
let recipeFound = false;
this.bitbakeProjectScanner.scanResult._recipes.forEach((recipe: ElementInfo) => {
// TODO fix resolving -native recipes (put that logic in a utility function) (could be shared with BitbakeRecipesView.getChildren)
// TODO fix resolving some packages like xz or busybox (only when in the bottom row?)
if (recipe.name === recipeName) {
if (recipe.path !== undefined) {
vscode.window.showTextDocument(vscode.Uri.file(path.format(recipe.path)));
recipeFound = true;
}
}
})
if (!recipeFound) {
vscode.window.showErrorMessage(`Project scan was not able to resolve ${recipeName}`);
}
}
}
8 changes: 8 additions & 0 deletions client/web/depends-dot/main.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
/* More variables in https://code.visualstudio.com/api/references/theme-color */

body {
background-color: transparent;
}

.packageLine:hover {
color: var(--vscode-list-hoverForeground);
background: var(--vscode-list-hoverBackground);
cursor: pointer;
}
15 changes: 8 additions & 7 deletions client/web/depends-dot/main.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,21 +34,21 @@
Button to generate the dependency graph
-->
<div>
<h3>Recipe graph:</h3>
<h3>Select an image <b>Recipe</b> to analyze:</h3>
<input type="text" id="graphRecipe" />
<button id="genDotFile">Generate dependency dotfile</button>
<button id="genDotFile">Gather dependency data</button>
</div>

<div>
<h3>Dependency type:</h3>
<h3>You want to know:</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>
<li><input type="radio" name="dependencyType" value="why" checked><h3><b>Why</b> a package is included</h3></li>
<li><input type="radio" name="dependencyType" value="depends"><h3>What it <b>Depends</b> on</h3></li>
</ul>
</div>

<div>
<h3>Package:</h3>
<h3>Select which <b>Package</b> you want to examine:</h3>
<input type="text" id="packageName" />
</div>

Expand All @@ -58,7 +58,8 @@ <h3>Package:</h3>

<div>
<h3>Results:</h3>
<textarea id="results" readonly></textarea>
</div>
<div id="results">
</div>

<script nonce="<%= nonce %>" src="<%= scriptUri %>"></script>
Expand Down
63 changes: 61 additions & 2 deletions client/web/depends-dot/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
const oldState = vscode.getState() || { };

// HTML Listeners
document.querySelector('#depType').addEventListener('click', () => {
document.querySelector('#depType').addEventListener('click', (event) => {
vscode.postMessage({ type: 'depType', value: event.target.value });
});

Expand All @@ -36,12 +36,71 @@
// Extension Listeners
window.addEventListener('message', event => {
const message = event.data; // The json data that the extension sent
// TODO remove empty final line, especially when asking for an inexistent package
switch (message.type) {
case 'results':
{
document.querySelector('#results').textContent = message.value;
if(message.depType === '-w') {
renderWhy(message, vscode);
} else {
renderDependencies(message, vscode);
}
break;
}
}
});
}());

function renderDependencies(message, vscode) {
const resultsDiv = document.querySelector('#results');
resultsDiv.innerHTML = '';

const packages = message.value.split(' ');
packages.forEach(pkg => {
addPackageLine(resultsDiv, pkg, '•', vscode);
});
}

function renderWhy(message, vscode) {
const resultsDiv = document.querySelector('#results');
resultsDiv.innerHTML = '';

const dependencyChains = message.value.split('\n');
for(let i = 0; i < dependencyChains.length; i++) {
const chain = dependencyChains[i];
const chainDiv = document.createElement('div');
chainDiv.className = 'dependencyChain';
resultsDiv.appendChild(chainDiv);
renderDependencyChain(chain, chainDiv, vscode);
}
}

function renderDependencyChain(chain, element, vscode) {
// Use the unicode box drawing characters to draw the lines
// https://www.compart.com/en/unicode/block/U+2500
const packages = chain.split(' -> ');
for(let i = 0; i < packages.length; i++) {
const pkg = packages[i];
let icon = '┃';
if(i === 0) { icon = '┳'; }
if(i === packages.length - 1) { icon = '┻'; }
addPackageLine(element, pkg, icon, vscode);
}
}

function addPackageLine(element, name, graphIcon, vscode) {
const div = document.createElement('div');
div.className = 'packageLine';
div.innerHTML = `<span class="graphIcon">${graphIcon}</span> ${name}`;
element.appendChild(div);
div.addEventListener('click', () => {
vscode.postMessage({ type: 'openRecipe', value: name });
});
}

function addIconLine(element, icon) {
const div = document.createElement('div');
div.className = 'iconLine';
div.innerHTML = `<span class="icon">${icon}</span>`;
element.appendChild(div);
}

0 comments on commit ea78e75

Please sign in to comment.