blob: 397d3d264c5caa4ee3e61dc304117f945b7828e2 [file] [log] [blame] [edit]
// Copyright 2024 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import { ChildProcessWithoutNullStreams, spawn } from 'child_process';
import { DataStreamProcess, JsonStreamProcess } from './process';
import * as logger from './logger';
import { Ffx } from './ffx';
export class Fx {
constructor(public fuchsiaDir: string | undefined, public readonly ffx: Ffx) {
}
/**
* Spawns a new process running fx with given arguments.
* @param args command line arguments to fx
* @returns ChildProcess is returned or undefined if there was a problem
*/
public runStreaming(args: string[]): ChildProcessWithoutNullStreams | undefined {
if (!this.fuchsiaDir) {
return;
}
let env = { ...process.env };
env['PATH'] = `${this.fuchsiaDir}/.jiri_root/bin:${env.PATH}`;
if (this.ffx.targetDevice) {
env['FUCHSIA_NODENAME'] = this.ffx.targetDevice.nodeName;
}
const options = { detached: true, cwd: this.fuchsiaDir, env };
return spawn('fx', args, options);
}
public runAsync(
args: string[],
onData: (data: Buffer) => void,
onError: (data: Buffer) => void): DataStreamProcess | undefined {
const process = this.runStreaming(args);
if (!process) {
return;
}
return new DataStreamProcess(process, onData, onError);
}
/**
* Return fx response data
* @param arg
* @returns stdout of command or error with stderr.
*/
public runFx(args: string[]): Promise<string> {
return new Promise<string>((resolve, reject) => {
let output = '';
const cmd = this.runStreaming(args);
cmd?.stdout.on('data', (data) => { output += data; });
let errorOutput = '';
cmd?.stderr.on('data', (data) => { errorOutput += data; });
// Keep track of the error state so that the 'close' event handler below won't handle the
// error again.
let hasError = false;
cmd?.on('error', err => {
logger.warn(`exit: ${err}`, 'ffx');
hasError = true;
return reject(err);
});
// Listen to 'close' instead of 'exit' event to correctly capture the output of ffx.
// The 'exit' event may be emitted when the stdio streams of the child process are still
// open, while the 'close' event is emitted after the stdio streams of a child process
// have been closed.
// See https://nodejs.org/api/child_process.html#class-childprocess for more details.
cmd?.on('close', (code, signal) => {
if (!hasError) {
logger.debug(`exit: ${code}: ${signal}`, 'ffx');
if (code === 0) {
return resolve(output);
} else {
return reject(
new Error(`fx returned with non-zero exit code ${code}: ${errorOutput}`));
}
}
});
});
}
/**
* Runs fx with given arguments and logs result.
* @param args command line arguments to fx
* @param onData when data is received this command will be called
* @returns fx log process
*/
public runJsonStreaming(
args: string[],
onData: (data: Object) => void
): JsonStreamProcess {
return new JsonStreamProcess(
this.runStreaming(args),
onData,
(data) => {
logger.warn(`Error [fx ${args}]: ${data}`);
});
}
}