From 2b1438b11a3a2ee2b90eb7def933808d8758b787 Mon Sep 17 00:00:00 2001 From: Enguerrand de Ribaucourt Date: Tue, 28 Nov 2023 16:23:25 -0500 Subject: [PATCH 1/9] Fix: Resolve layer path in container If a docker container commandWrapper is used, the workdir may be different from the host system. This patch resolves the path to the host system by finding the inode of the host workdir in the container. --- client/src/driver/BitBakeProjectScanner.ts | 62 ++++++++++++++++++++-- 1 file changed, 58 insertions(+), 4 deletions(-) diff --git a/client/src/driver/BitBakeProjectScanner.ts b/client/src/driver/BitBakeProjectScanner.ts index c7d8b4c6..56835f7d 100644 --- a/client/src/driver/BitBakeProjectScanner.ts +++ b/client/src/driver/BitBakeProjectScanner.ts @@ -20,6 +20,7 @@ import type { import { type BitbakeDriver } from './BitbakeDriver' import { type LanguageClient } from 'vscode-languageclient/node' +import fs from 'fs' interface ScannStatus { scanIsRunning: boolean @@ -39,6 +40,7 @@ export class BitBakeProjectScanner { private _shouldDeepExamine: boolean = false private _bitbakeDriver: BitbakeDriver | undefined private _languageClient: LanguageClient | undefined + private containerWorkdir: string | undefined setDriver (bitbakeDriver: BitbakeDriver): void { this._bitbakeDriver = bitbakeDriver @@ -109,6 +111,37 @@ export class BitBakeProjectScanner { } } + private async getContainerInode (filepath: string): Promise { + const commandResult = await this.executeBitBakeCommand(`stat -c %i ${filepath}`) + const inode = parseInt(commandResult.stdout.toString().trim()) + return inode + } + + private async scanContainerWorkdir (layerPath: string, hostWorkdir: string): Promise { + if (fs.existsSync(layerPath)) { + // We're not inside a container, or the container is not using a different workdir + return + } + + // Get inode of hostWorkdir + const hostWorkdirStats = fs.statSync(hostWorkdir) + const hostWorkdirInode = hostWorkdirStats.ino + + // Find inode in layerPath and all parents + let parentInode = NaN + let parentPath = layerPath + while (parentPath !== '/') { + parentInode = await this.getContainerInode(parentPath) + if (parentInode === hostWorkdirInode) { + this.containerWorkdir = parentPath + return + } + parentPath = path.dirname(parentPath) + } + + this.containerWorkdir = undefined + } + private printScanStatistic (): void { logger.info('Scan results:') logger.info('******************************************************************') @@ -129,6 +162,7 @@ export class BitBakeProjectScanner { private async scanAvailableLayers (): Promise { this._bitbakeScanResult._layers = new Array < LayerInfo >() + this.containerWorkdir = undefined const commandResult = await this.executeBitBakeCommand('bitbake-layers show-layers') @@ -145,15 +179,15 @@ export class BitBakeProjectScanner { } for (const element of outputLines.slice(layersFirstLine + 2)) { - const tempElement: string[] = element.split(/\s+/) + const tempElement = element.split(/\s+/) const layerElement = { name: tempElement[0], - path: tempElement[1], + path: await this.resolveContainerPath(tempElement[1]), priority: parseInt(tempElement[2]) } - if ((layerElement.name !== undefined) && (layerElement.path !== undefined) && layerElement.priority !== undefined) { - this._bitbakeScanResult._layers.push(layerElement) + if ((layerElement.name !== undefined) && (layerElement.path !== undefined) && (layerElement.priority !== undefined)) { + this._bitbakeScanResult._layers.push(layerElement as LayerInfo) } } } else { @@ -162,6 +196,26 @@ export class BitBakeProjectScanner { } } + /// If a docker container is used, the workdir may be different from the host system. + /// This function resolves the path to the host system. + private async resolveContainerPath (layerPath: string | undefined): Promise { + if (layerPath === undefined) { + return undefined + } + const hostWorkdir = this.bitbakeDriver?.bitbakeSettings.workingDirectory + if (hostWorkdir === undefined) { + throw new Error('hostWorkdir is undefined') + } + if (this.containerWorkdir === undefined) { + await this.scanContainerWorkdir(layerPath, hostWorkdir) + } + if (this.containerWorkdir === undefined) { + return layerPath + } + const relativePath = path.relative(this.containerWorkdir, layerPath) + return path.resolve(hostWorkdir, relativePath) + } + private searchFiles (pattern: string): ElementInfo[] { const elements: ElementInfo[] = new Array < ElementInfo >() From b42c642ba2af2a1dd69caed5d3c208206ef3bd06 Mon Sep 17 00:00:00 2001 From: Enguerrand de Ribaucourt Date: Tue, 5 Dec 2023 14:33:44 +0100 Subject: [PATCH 2/9] Fix: Don't check for env script if command wrapper is set In the case of a docker container commandWrapper, the env script may exist in a different location than the one on the host. So don't check for the env script if a command wrapper is set. --- client/src/driver/BitbakeDriver.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/driver/BitbakeDriver.ts b/client/src/driver/BitbakeDriver.ts index 51f9ada3..54d45f67 100644 --- a/client/src/driver/BitbakeDriver.ts +++ b/client/src/driver/BitbakeDriver.ts @@ -109,7 +109,7 @@ export class BitbakeDriver { } const pathToEnvScript = this.bitbakeSettings.pathToEnvScript - if (pathToEnvScript !== undefined && !fs.existsSync(pathToEnvScript)) { + if (this.bitbakeSettings.commandWrapper === undefined && pathToEnvScript !== undefined && !fs.existsSync(pathToEnvScript)) { clientNotificationManager.showBitbakeError("Bitbake environment script doesn't exist: " + pathToEnvScript) return false } From 5fae952155497d1e808959fc848cff78c961d79d Mon Sep 17 00:00:00 2001 From: Enguerrand de Ribaucourt Date: Tue, 5 Dec 2023 14:35:12 +0100 Subject: [PATCH 3/9] Fix: Parsing of inode number whith additional logs When going through a commandWrapper, some logs may be injected. This is the case with poky/crops. This patch fixes the parsing of the inode number by using a regex. --- client/src/driver/BitBakeProjectScanner.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/src/driver/BitBakeProjectScanner.ts b/client/src/driver/BitBakeProjectScanner.ts index 56835f7d..8cea0464 100644 --- a/client/src/driver/BitBakeProjectScanner.ts +++ b/client/src/driver/BitBakeProjectScanner.ts @@ -113,7 +113,10 @@ export class BitBakeProjectScanner { private async getContainerInode (filepath: string): Promise { const commandResult = await this.executeBitBakeCommand(`stat -c %i ${filepath}`) - const inode = parseInt(commandResult.stdout.toString().trim()) + const stdout = commandResult.stdout.toString().trim() + const regex = /^\d+$/m + const match = stdout.match(regex) + const inode = (match != null) ? parseInt(match[0]) : NaN return inode } From b3780547674bcdd2c04d1b31540e5dc1a62f06ee Mon Sep 17 00:00:00 2001 From: Enguerrand de Ribaucourt Date: Tue, 5 Dec 2023 16:08:58 +0100 Subject: [PATCH 4/9] Feature: Add environment variables customization This patch adds a new setting `bitbake.shellEnv` which allows to set environment variables before running the BitBake command. This allows customizing oe-init-build-env but also kas, cqfd and other build tools. --- client/package.json | 5 +++++ client/src/driver/BitbakeDriver.ts | 6 ++++-- client/src/lib/src/BitbakeSettings.ts | 8 +++++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/client/package.json b/client/package.json index 44313a2d..f9e1564f 100644 --- a/client/package.json +++ b/client/package.json @@ -124,6 +124,11 @@ "type": "string", "default": "${workspaceFolder}", "description": "Set the working directory for running BitBake command." + }, + "bitbake.shellEnv": { + "type": "object", + "default": {}, + "description": "Environment variables to set before running the BitBake command." } } }, diff --git a/client/src/driver/BitbakeDriver.ts b/client/src/driver/BitbakeDriver.ts index 54d45f67..e3f69eac 100644 --- a/client/src/driver/BitbakeDriver.ts +++ b/client/src/driver/BitbakeDriver.ts @@ -39,7 +39,8 @@ export class BitbakeDriver { this.bitbakeActive = true const child = childProcess.spawn(script, { shell, - cwd: this.bitbakeSettings.workingDirectory + cwd: this.bitbakeSettings.workingDirectory, + env: { ...process.env, ...this.bitbakeSettings.shellEnv } }) child.on('close', () => { this.bitbakeActive = false @@ -55,7 +56,8 @@ export class BitbakeDriver { this.bitbakeActive = true const ret = childProcess.spawnSync(script, { shell, - cwd: this.bitbakeSettings.workingDirectory + cwd: this.bitbakeSettings.workingDirectory, + env: { ...process.env, ...this.bitbakeSettings.shellEnv } }) this.bitbakeActive = false return ret diff --git a/client/src/lib/src/BitbakeSettings.ts b/client/src/lib/src/BitbakeSettings.ts index cf444b6e..55fad125 100644 --- a/client/src/lib/src/BitbakeSettings.ts +++ b/client/src/lib/src/BitbakeSettings.ts @@ -12,6 +12,7 @@ export interface BitbakeSettings { pathToEnvScript?: string commandWrapper?: string workingDirectory: string + shellEnv?: NodeJS.Dict } export function loadBitbakeSettings (settings: any, workspaceFolder: string): BitbakeSettings { @@ -29,7 +30,8 @@ export function loadBitbakeSettings (settings: any, workspaceFolder: string): Bi pathToBuildFolder: settings.pathToBuildFolder !== '' ? resolveSettingsPath(settings.pathToBuildFolder, workspaceFolder) : undefined, pathToEnvScript: settings.pathToEnvScript !== '' ? resolveSettingsPath(settings.pathToEnvScript, workspaceFolder) : undefined, commandWrapper: settings.commandWrapper !== '' ? expandWorkspaceFolder(settings.commandWrapper, workspaceFolder) : undefined, - workingDirectory: settings.workingDirectory !== '' ? resolveSettingsPath(settings.workingDirectory, workspaceFolder) : workspaceFolder + workingDirectory: settings.workingDirectory !== '' ? resolveSettingsPath(settings.workingDirectory, workspaceFolder) : workspaceFolder, + shellEnv: toStringDict(settings.shellEnv) } } @@ -48,3 +50,7 @@ function sanitizeForShell (command: string | undefined): string | undefined { } return command.replace(/[;`&|<>\\$(){}!#*?"']/g, '') } + +function toStringDict (dict: object | undefined): NodeJS.Dict | undefined { + return dict as NodeJS.Dict | undefined +} From f07d292fea7ac66577fe66726a8fe03314cced16 Mon Sep 17 00:00:00 2001 From: Enguerrand de Ribaucourt Date: Tue, 5 Dec 2023 16:17:40 +0100 Subject: [PATCH 5/9] Doc: Document bitbake.shellEnv option --- client/README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/client/README.md b/client/README.md index 9ac4922f..33cc84d7 100644 --- a/client/README.md +++ b/client/README.md @@ -26,8 +26,9 @@ Or, if `pathToEnvScript` and `pathToBuildFolder` are defined: ```shell $ $commandWrapper ". $pathToEnvScript $pathToBuildFolder && bitbake core-image-minimal" ``` +You can also control the directory from which they are started by using the `bitbake.workingDirectory` option as well as the shell environment variables with `bitbake.shellEnv`. -Here are some examples using the most popular bitbake wrappers. You can also control the directory from which they are started by using the `bitbake.workingDirectory` option: +Here are some examples using the most popular bitbake wrappers: ```json { "bitbake.commandWrapper": "docker run --rm -v ${workspaceFolder}:${workspaceFolder} crops/poky --workdir=${workspaceFolder} /bin/bash -c", @@ -38,7 +39,12 @@ Here are some examples using the most popular bitbake wrappers. You can also con "bitbake.commandWrapper": "kas shell -c", "bitbake.workingDirectory": "${workspaceFolder}/yocto" } -{ "bitbake.commandWrapper": "cqfd run" } +{ + "bitbake.commandWrapper": "cqfd run", + "bitbake.shellEnv": { + "CQFD_EXTRA_RUN_ARGS": "-e DISPLAY=:0 -v /tmp/.X11-unix:/tmp/.X11-unix" + } +} { "bitbake.commandWrapper": "${workspaceFolder}/build.sh --" } ``` From a7adcbb77c75bfdbbc4b24a5567cda0890d55d93 Mon Sep 17 00:00:00 2001 From: Enguerrand de Ribaucourt Date: Tue, 5 Dec 2023 17:43:42 +0100 Subject: [PATCH 6/9] Test: Add a test for the commandWrapper setting This test verifies callink bitbake from a docker container with a complex mount point. --- .gitignore | 2 +- client/src/driver/BitBakeProjectScanner.ts | 7 ++ .../sources/meta-fixtures/definition.bb | 1 + .../src/tests/command-wrapper.test.ts | 74 +++++++++++++++++++ package.json | 2 +- 5 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 integration-tests/project-folder/sources/meta-fixtures/definition.bb create mode 100644 integration-tests/src/tests/command-wrapper.test.ts diff --git a/.gitignore b/.gitignore index ef6e4152..a292b5bd 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,4 @@ coverage/ resources/ client/resources/ -integration-tests/project-folder/build +integration-tests/project-folder/build* diff --git a/client/src/driver/BitBakeProjectScanner.ts b/client/src/driver/BitBakeProjectScanner.ts index 8cea0464..aa8d75b6 100644 --- a/client/src/driver/BitBakeProjectScanner.ts +++ b/client/src/driver/BitBakeProjectScanner.ts @@ -142,6 +142,13 @@ export class BitBakeProjectScanner { parentPath = path.dirname(parentPath) } + /* + * FIXME This works in most cases. + * We could provide an additional bitbake.containerWorkdir setting to provide path resolution hints if this fails. + * One example is having a layer that is a symlink to an external directory from the working directory. + * Ex: 'commandWrapper': 'docker run --rm -v ${workspaceFolder}/../..:/workdir/ crops/poky --workdir=/workdir /bin/bash -c' + */ + this.containerWorkdir = undefined } diff --git a/integration-tests/project-folder/sources/meta-fixtures/definition.bb b/integration-tests/project-folder/sources/meta-fixtures/definition.bb new file mode 100644 index 00000000..c8d6f49e --- /dev/null +++ b/integration-tests/project-folder/sources/meta-fixtures/definition.bb @@ -0,0 +1 @@ +inherit image diff --git a/integration-tests/src/tests/command-wrapper.test.ts b/integration-tests/src/tests/command-wrapper.test.ts new file mode 100644 index 00000000..878098ca --- /dev/null +++ b/integration-tests/src/tests/command-wrapper.test.ts @@ -0,0 +1,74 @@ +/* -------------------------------------------------------------------------------------------- + * 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 assert from 'assert' +import * as vscode from 'vscode' + +import { delay } from '../utils/async' +import path from 'path' + +suite('Bitbake Command Wrapper', () => { + let workspaceURI: vscode.Uri + let buildFolder: vscode.Uri + let savedSettings: { + pathToBuildFolder: string | undefined + pathToEnvScript: string | undefined + } + + suiteSetup(async function (this: Mocha.Context) { + /* eslint-disable no-template-curly-in-string */ + this.timeout(100000) + while (vscode.workspace.workspaceFolders === undefined || vscode.workspace.workspaceFolders?.length === 0) { + await delay(100) + } + workspaceURI = vscode.workspace.workspaceFolders[0].uri + buildFolder = vscode.Uri.joinPath(workspaceURI, 'build-crops') + + const vscodeBitbake = vscode.extensions.getExtension('yocto-project.yocto-bitbake') + if (vscodeBitbake === undefined) { + assert.fail('Bitbake extension is not available') + } + await vscodeBitbake.activate() + + const bitbakeConfiguration = vscode.workspace.getConfiguration('bitbake') + savedSettings = { + pathToBuildFolder: bitbakeConfiguration.get('pathToBuildFolder'), + pathToEnvScript: bitbakeConfiguration.get('pathToEnvScript') + } + + // We use purposely complex mount points to test the scanner path resolution logic + await bitbakeConfiguration.update('commandWrapper', 'docker run --rm -v ${workspaceFolder}/../..:/workdir/ crops/poky --workdir=/workdir /bin/bash -c') + await bitbakeConfiguration.update('pathToBuildFolder', '/workdir/integration-tests/project-folder/build-crops') + await bitbakeConfiguration.update('pathToEnvScript', '/workdir/integration-tests/project-folder/sources/poky/oe-init-build-env') + }) + + suiteTeardown(async function (this: Mocha.Context) { + await vscode.workspace.fs.delete(buildFolder, { recursive: true }) + + const bitbakeConfiguration = vscode.workspace.getConfiguration('bitbake') + await bitbakeConfiguration.update('commandWrapper', undefined) + await bitbakeConfiguration.update('pathToBuildFolder', savedSettings.pathToBuildFolder) + await bitbakeConfiguration.update('pathToEnvScript', savedSettings.pathToEnvScript) + }) + + test('Bitbake can properly scan includes inside a crops container', async () => { + const filePath = path.resolve(__dirname, '../../project-folder/sources/meta-fixtures/definition.bb') + const docUri = vscode.Uri.parse(`file://${filePath}`) + let definitions: vscode.Location[] = [] + + await vscode.workspace.openTextDocument(docUri) + + while (definitions.length !== 1) { + definitions = await vscode.commands.executeCommand( + 'vscode.executeDefinitionProvider', + docUri, + new vscode.Position(0, 10) + ) + + // We cannot get an event when the BitBake scan is complete, so we have to wait for it to finish and hope for the best + await delay(100) + } + }).timeout(300000) +}) diff --git a/package.json b/package.json index e8c00a1d..06c1d3f5 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "clean:server": "rm -fr ./server/node_modules ./client/server server/tsconfig.tsbuildinfo server/.vscode-test", "clean:client": "rm -fr ./client/node_modules ./client/out client/tsconfig.tsbuildinfo client/.vscode-test client/*.vsix", "clean:lib": "rm -fr ./lib/node_modules", - "clean": "npm run clean:lib && npm run clean:server && npm run clean:client && rm -fr node_modules integration-tests/out .vscode-test .eslintcache resources/poky* resources/docs coverage ./out", + "clean": "npm run clean:lib && npm run clean:server && npm run clean:client && rm -fr node_modules integration-tests/out integration-tests/project-folder/build* .vscode-test .eslintcache resources/poky* resources/docs coverage ./out", "lint": "eslint . --ext js,ts --cache", "jest": "jest", "test": "npm run jest && npm run test:integration && cd client && npm run test:grammar", From e760df80deb8e1f7a6743da9d55087e94fbf199f Mon Sep 17 00:00:00 2001 From: Enguerrand de Ribaucourt Date: Wed, 6 Dec 2023 11:16:02 +0100 Subject: [PATCH 7/9] Fix: Layer container resolution for symlinks If the mount point was a parent directory of the bitbake.workingDir, and the layers were symlinks to directories outside of the workingDir, the layer resolution would fail. We now itterate on the host parent directories inodes also. --- client/src/driver/BitBakeProjectScanner.ts | 63 +++++++++++----------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/client/src/driver/BitBakeProjectScanner.ts b/client/src/driver/BitBakeProjectScanner.ts index aa8d75b6..825de2ab 100644 --- a/client/src/driver/BitBakeProjectScanner.ts +++ b/client/src/driver/BitBakeProjectScanner.ts @@ -40,7 +40,10 @@ export class BitBakeProjectScanner { private _shouldDeepExamine: boolean = false private _bitbakeDriver: BitbakeDriver | undefined private _languageClient: LanguageClient | undefined - private containerWorkdir: string | 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 { this._bitbakeDriver = bitbakeDriver @@ -120,36 +123,36 @@ export class BitBakeProjectScanner { return inode } - private async scanContainerWorkdir (layerPath: string, hostWorkdir: string): Promise { + private async scanContainerMountPoint (layerPath: string, hostWorkdir: string): Promise { + this.containerMountPoint = undefined + this.hostMountPoint = undefined + if (fs.existsSync(layerPath)) { // We're not inside a container, or the container is not using a different workdir return } - // Get inode of hostWorkdir - const hostWorkdirStats = fs.statSync(hostWorkdir) - const hostWorkdirInode = hostWorkdirStats.ino - - // Find inode in layerPath and all parents - let parentInode = NaN - let parentPath = layerPath - while (parentPath !== '/') { - parentInode = await this.getContainerInode(parentPath) - if (parentInode === hostWorkdirInode) { - this.containerWorkdir = parentPath - return + let hostDir = hostWorkdir + + while (hostDir !== '/') { + const hostDirInode = fs.statSync(hostDir).ino + + // Find inode in layerPath and all parents + // OPTIM we could run stat on all parent directories in one (find?) command, and store the result in a map + let containerDirInode = NaN + let containerDir = layerPath + while (containerDir !== '/') { + containerDirInode = await this.getContainerInode(containerDir) + logger.debug('Comparing container inodes: ' + containerDir + ':' + containerDirInode + ' ' + hostDir + ':' + hostDirInode) + if (containerDirInode === hostDirInode) { + this.containerMountPoint = containerDir + this.hostMountPoint = hostDir + return + } + containerDir = path.dirname(containerDir) } - parentPath = path.dirname(parentPath) + hostDir = path.dirname(hostDir) } - - /* - * FIXME This works in most cases. - * We could provide an additional bitbake.containerWorkdir setting to provide path resolution hints if this fails. - * One example is having a layer that is a symlink to an external directory from the working directory. - * Ex: 'commandWrapper': 'docker run --rm -v ${workspaceFolder}/../..:/workdir/ crops/poky --workdir=/workdir /bin/bash -c' - */ - - this.containerWorkdir = undefined } private printScanStatistic (): void { @@ -172,7 +175,7 @@ export class BitBakeProjectScanner { private async scanAvailableLayers (): Promise { this._bitbakeScanResult._layers = new Array < LayerInfo >() - this.containerWorkdir = undefined + this.containerMountPoint = undefined const commandResult = await this.executeBitBakeCommand('bitbake-layers show-layers') @@ -216,14 +219,14 @@ export class BitBakeProjectScanner { if (hostWorkdir === undefined) { throw new Error('hostWorkdir is undefined') } - if (this.containerWorkdir === undefined) { - await this.scanContainerWorkdir(layerPath, hostWorkdir) + if (this.containerMountPoint === undefined) { + await this.scanContainerMountPoint(layerPath, hostWorkdir) } - if (this.containerWorkdir === undefined) { + if (this.containerMountPoint === undefined || this.hostMountPoint === undefined) { return layerPath } - const relativePath = path.relative(this.containerWorkdir, layerPath) - return path.resolve(hostWorkdir, relativePath) + const relativePath = path.relative(this.containerMountPoint, layerPath) + return path.resolve(this.hostMountPoint, relativePath) } private searchFiles (pattern: string): ElementInfo[] { From 2c25cc380ad63951e2aa11f2621d32e175894d47 Mon Sep 17 00:00:00 2001 From: Enguerrand de Ribaucourt Date: Wed, 6 Dec 2023 11:54:09 +0100 Subject: [PATCH 8/9] Optim: Get all container inodes in one find command This greatly speeds up the scanner when we are looking for corresponding container mount points. The scanner now runs only one find command instead of one per directory level in the layer path. --- client/src/driver/BitBakeProjectScanner.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/client/src/driver/BitBakeProjectScanner.ts b/client/src/driver/BitBakeProjectScanner.ts index 825de2ab..128a9ed9 100644 --- a/client/src/driver/BitBakeProjectScanner.ts +++ b/client/src/driver/BitBakeProjectScanner.ts @@ -114,15 +114,15 @@ export class BitBakeProjectScanner { } } - private async getContainerInode (filepath: string): Promise { - const commandResult = await this.executeBitBakeCommand(`stat -c %i ${filepath}`) + private async getContainerParentInodes (filepath: string): Promise { + const commandResult = await this.executeBitBakeCommand(`f=${filepath}; while [[ $f != / ]]; do stat -c %i $f; f=$(dirname "$f"); done;`) const stdout = commandResult.stdout.toString().trim() - const regex = /^\d+$/m - const match = stdout.match(regex) - const inode = (match != null) ? parseInt(match[0]) : NaN - return inode + const regex = /^\d+$/gm + const matches = stdout.match(regex) + return (matches != null) ? matches.map((match) => parseInt(match)) : [NaN] } + /// Find corresponding mount point inode in layerPath/hostWorkdir and all parents private async scanContainerMountPoint (layerPath: string, hostWorkdir: string): Promise { this.containerMountPoint = undefined this.hostMountPoint = undefined @@ -132,17 +132,16 @@ export class BitBakeProjectScanner { return } + let containerDir = layerPath + const containerDirInodes = await this.getContainerParentInodes(containerDir) let hostDir = hostWorkdir while (hostDir !== '/') { const hostDirInode = fs.statSync(hostDir).ino - // Find inode in layerPath and all parents - // OPTIM we could run stat on all parent directories in one (find?) command, and store the result in a map - let containerDirInode = NaN - let containerDir = layerPath + let containerIdx = 0 while (containerDir !== '/') { - containerDirInode = await this.getContainerInode(containerDir) + const containerDirInode = containerDirInodes[containerIdx] logger.debug('Comparing container inodes: ' + containerDir + ':' + containerDirInode + ' ' + hostDir + ':' + hostDirInode) if (containerDirInode === hostDirInode) { this.containerMountPoint = containerDir @@ -150,6 +149,7 @@ export class BitBakeProjectScanner { return } containerDir = path.dirname(containerDir) + containerIdx++ } hostDir = path.dirname(hostDir) } From 9c769a29c802b43750c47bc14a729ed47192935f Mon Sep 17 00:00:00 2001 From: Enguerrand de Ribaucourt Date: Wed, 6 Dec 2023 18:00:22 +0100 Subject: [PATCH 9/9] Doc: Recommend excluding Yocto build directory parsing --- client/README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/client/README.md b/client/README.md index 33cc84d7..be4b98c0 100644 --- a/client/README.md +++ b/client/README.md @@ -48,6 +48,31 @@ Here are some examples using the most popular bitbake wrappers: { "bitbake.commandWrapper": "${workspaceFolder}/build.sh --" } ``` +### Additional settings recommendations + +If your workspace contains a Yocto build directory, some other extensions may +be hogging lots of resources to parse it's contents and stall your machine. Here are +some example settings to improve your experience (assuming your build directory +is called `build`). You can add them to your `settings.json` file, or your global +user settings. + +```json +{ + "files.watcherExclude": { + "**/build/**": true + }, + "search.exclude": { + "**/build/**": true + }, + "C_Cpp.files.exclude": { + "**/build": true + }, + "python.analysis.exclude": [ + "**/build/**" + ] +} +``` + ## Features ### Syntax highlighting