Skip to content

Commit

Permalink
Fix: Use dynamic imports for node-pty
Browse files Browse the repository at this point in the history
VSCode and the Jest native environment use conflicting
NODE_MODULE_VERSION. We have to use node-pty from VSCode
when running in the VSCode environment and the regular
node-pty when running in Jest.

Following the idea presented in:
microsoft/vscode#658 (comment)
We use dynamic imports to load the correct node-pty.

The types must still be imported normally at compile time.
  • Loading branch information
deribaucourt committed Feb 20, 2024
1 parent e88d19f commit b76ea15
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 13 deletions.
9 changes: 5 additions & 4 deletions client/src/driver/BitbakeDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,21 @@
* ------------------------------------------------------------------------------------------ */

import EventEmitter from 'events'
import * as pty from 'node-pty'

import { logger } from '../lib/src/utils/OutputLogger'
import { type BitbakeSettings, loadBitbakeSettings, sanitizeForShell } from '../lib/src/BitbakeSettings'
import { clientNotificationManager } from '../ui/ClientNotificationManager'
import { type BitbakeTaskDefinition } from '../ui/BitbakeTaskProvider'
import { runBitbakeTerminalCustomCommand } from '../ui/BitbakeTerminal'
import { bitbakeESDKMode, setBitbakeESDKMode } from './BitbakeESDK'
import { BITBAKE_EXIT_TIMEOUT, finishProcessExecution } from '../utils/ProcessUtils'
import { BITBAKE_EXIT_TIMEOUT, finishProcessExecution, pty } from '../utils/ProcessUtils'

import { type IPty } from 'node-pty'

/// This class is responsible for wrapping up all bitbake classes and exposing them to the extension
export class BitbakeDriver {
bitbakeSettings: BitbakeSettings = { pathToBitbakeFolder: '', pathToBuildFolder: '', pathToEnvScript: '', workingDirectory: '', commandWrapper: '' }
bitbakeProcess: pty.IPty | undefined
bitbakeProcess: IPty | undefined
bitbakeProcessCommand: string | undefined
onBitbakeProcessChange: EventEmitter = new EventEmitter()

Expand All @@ -38,7 +39,7 @@ export class BitbakeDriver {
}

/// Execute a command in the bitbake environment
async spawnBitbakeProcess (command: string): Promise<pty.IPty> {
async spawnBitbakeProcess (command: string): Promise<IPty> {
const { shell, script } = this.prepareCommand(command)
const cwd = this.bitbakeSettings.workingDirectory
await this.waitForBitbakeToFinish()
Expand Down
12 changes: 6 additions & 6 deletions client/src/ui/BitbakeTerminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */

import { type IPty } from 'node-pty'
import * as vscode from 'vscode'
import type * as pty from 'node-pty'
import { logger } from '../lib/src/utils/OutputLogger'
import path from 'path'
import { type BitbakeDriver } from '../driver/BitbakeDriver'
Expand All @@ -14,20 +14,20 @@ const endOfLine: string = '\r\n'
const emphasisedAsterisk: string = '\x1b[7m * \x1b[0m'

/// Spawn a bitbake process in a dedicated terminal and wait for it to finish
export async function runBitbakeTerminal (bitbakeDriver: BitbakeDriver, bitbakeTaskDefinition: BitbakeTaskDefinition, terminalName: string, isBackground: boolean = false): Promise<pty.IPty> {
export async function runBitbakeTerminal (bitbakeDriver: BitbakeDriver, bitbakeTaskDefinition: BitbakeTaskDefinition, terminalName: string, isBackground: boolean = false): Promise<IPty> {
const command = bitbakeDriver.composeBitbakeCommand(bitbakeTaskDefinition)
const script = bitbakeDriver.composeBitbakeScript(command)
return await runBitbakeTerminalScript(command, bitbakeDriver, terminalName, script, isBackground)
}

/// Spawn a bitbake process in a dedicated terminal and wait for it to finish
export async function runBitbakeTerminalCustomCommand (bitbakeDriver: BitbakeDriver, command: string, terminalName: string, isBackground: boolean = false): Promise<pty.IPty> {
export async function runBitbakeTerminalCustomCommand (bitbakeDriver: BitbakeDriver, command: string, terminalName: string, isBackground: boolean = false): Promise<IPty> {
const script = bitbakeDriver.composeBitbakeScript(command)
return await runBitbakeTerminalScript(command, bitbakeDriver, terminalName, script, isBackground)
}

const bitbakeTerminals: BitbakeTerminal[] = []
async function runBitbakeTerminalScript (command: string, bitbakeDriver: BitbakeDriver, terminalName: string, bitbakeScript: string, isBackground: boolean): Promise<pty.IPty> {
async function runBitbakeTerminalScript (command: string, bitbakeDriver: BitbakeDriver, terminalName: string, bitbakeScript: string, isBackground: boolean): Promise<IPty> {
let terminal: BitbakeTerminal | undefined
for (const t of bitbakeTerminals) {
if (!t.pty.isBusy()) {
Expand Down Expand Up @@ -94,7 +94,7 @@ export class BitbakePseudoTerminal implements vscode.Pseudoterminal {
}
}

private process: Promise<pty.IPty> | undefined
private process: Promise<IPty> | undefined

isBusy (): boolean {
return this.process !== undefined
Expand All @@ -109,7 +109,7 @@ export class BitbakePseudoTerminal implements vscode.Pseudoterminal {
this.writeEmitter.fire(line)
}

public async runProcess (process: Promise<pty.IPty>, bitbakeScript: string, terminalName?: string): Promise<pty.IPty> {
public async runProcess (process: Promise<IPty>, bitbakeScript: string, terminalName?: string): Promise<IPty> {
if (this.process !== undefined) {
throw new Error('Bitbake process already running')
}
Expand Down
30 changes: 27 additions & 3 deletions client/src/utils/ProcessUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,41 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */

import type * as pty from 'node-pty'
import * as vscode from 'vscode'
import type childProcess from 'child_process'
import { logger } from '../lib/src/utils/OutputLogger'
import type * as nodepty from 'node-pty'

function importFromVSCode (id: string): NodeRequire {
/* Inspired by https://github.com/microsoft/vscode/issues/658#issuecomment-982842847
* VSCode uses electron with a custom NODE_MODULE_VERSION and it's own node-pty version
* We need to use the same node-pty version as VSCode to avoid compatibility issues
* Meanwhile, under jest, we need to use the regular node-pty version
* Types still need to be imported normally at compile time
*/
try {
return require(`${vscode.env.appRoot}/node_modules.asar/${id}`)
} catch (err) {
// ignore
}
try {
return require(`${vscode.env.appRoot}/node_modules/${id}`)
} catch (err) {
// ignore
}
return require(id)
}

// The conversion allows the linter to understand the type of the imported module
export const pty = importFromVSCode('node-pty') as unknown as typeof nodepty

export const BITBAKE_TIMEOUT = 300000
export const BITBAKE_EXIT_TIMEOUT = 30000

export type KillProcessFunction = (child: pty.IPty) => Promise<void>
export type KillProcessFunction = (child: nodepty.IPty) => Promise<void>

/// Wait for an asynchronous process to finish and return its output
export async function finishProcessExecution (process: Promise<pty.IPty>, timeoutCallback: KillProcessFunction = async (child) => { child.kill() }, timeout = BITBAKE_TIMEOUT): Promise<childProcess.SpawnSyncReturns<Buffer>> {
export async function finishProcessExecution (process: Promise<nodepty.IPty>, timeoutCallback: KillProcessFunction = async (child) => { child.kill() }, timeout = BITBAKE_TIMEOUT): Promise<childProcess.SpawnSyncReturns<Buffer>> {
return await new Promise<childProcess.SpawnSyncReturns<Buffer>>((resolve, reject) => {
process.then((child) => {
let stdout = ''
Expand Down

0 comments on commit b76ea15

Please sign in to comment.