blob: 8dbdd5a784d4461bfabecfceb616bc29854841b1 [file] [log] [blame] [edit]
// Copyright 2025 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 * as vscode from 'vscode';
import { window } from 'vscode';
import { Setup } from '../extension';
import * as logger from '../logger';
import { registerCommandWithAnalyticsEvent } from '../analytics/vscode_events';
import { FuchsiaDiagnostics } from './problem_matcher';
export const BUILD_MATCHER = /^\[.*?\(\d+\)/;
/**
* Parameters for the logOutput function
*/
type OutputParams = {
message: string;
category: string;
inMatcher: boolean;
logChannel?: logger.Logger;
};
/**
* Determine log level to log output messages for fx build.
* @param output to sort into log level
* @param inMatcher If output is in build targets. There are cases
* where it appears in the file directory and should not output as an error.
* @param logChannel Defaults to the extension logOutputChannel.
*/
export function logOutput(options: OutputParams) {
const msg = options.message.toLowerCase();
const outputChannel = options.logChannel ? options.logChannel : logger;
if (msg.includes('failed: [code=1]')) {
outputChannel.error(options.message, options.category);
return;
}
if (msg.includes('error') || msg.includes('failed')) {
options.inMatcher ?
outputChannel.info(options.message, options.category) :
outputChannel.error(options.message, options.category);
return;
}
if (msg.includes('warn')) {
outputChannel.warn(options.message, options.category);
return;
}
outputChannel.info(options.message, options.category);
}
/**
* Handles data from the build process output.
* @param buffer The output buffer.
* @param category The category for logging.
* @param collection The diagnostics collection.
* @param progress The progress indicator.
* @param percentage The percentage object.
*/
function handleData(
buffer: Buffer,
category: string,
collection: FuchsiaDiagnostics,
progress: vscode.Progress<{ message?: string; increment?: number }>,
percentage: { value: number }
) {
const msg = new TextDecoder().decode(buffer);
const match = msg.match(BUILD_MATCHER);
logOutput({ message: msg, category, inMatcher: !!match });
collection.match(msg);
// Surface to user since msg can get lost in output window
if (msg.includes('Locked')) {
void window.showWarningMessage(msg);
}
if (match && msg.includes('%')) {
const percentMatch = msg.match(/\d+%/);
if (percentMatch) {
const currentPercentage = parseInt(percentMatch[0]);
const increment = currentPercentage - percentage.value;
percentage.value = currentPercentage;
progress.report({
increment,
message: `[(details)](command:fuchsia.showOutput) ${match[0]}`,
});
}
}
}
/**
* Registers the `fuchsia.fx.build` command.
*/
function registerFxBuild(setup: Setup, collection: FuchsiaDiagnostics) {
registerCommandWithAnalyticsEvent('fuchsia.fx.build', () => {
collection.clear();
void window.withProgress(
{
location: vscode.ProgressLocation.Notification,
title: 'Building Fuchsia',
cancellable: true,
},
async (progress, token) => {
const percentage = { value: 0 };
const cmd = setup.fx.runAsync(
['build'],
(buffer) => handleData(buffer, 'fx build', collection, progress, percentage),
(buffer) => handleData(buffer, 'fx build', collection, progress, percentage)
);
token.onCancellationRequested(() => {
logger.info('fx build stopped: user cancelled operation', 'fx build');
cmd?.stop();
});
const exitCode = await cmd?.exitCode;
if (exitCode === 0) {
logger.info('fx build complete: success [code=0]', 'fx build');
return Promise.resolve();
}
if (exitCode === 1) {
logger.error('fx build stopped: failed [code=1]', 'fx build');
void window.showErrorMessage(
'Build stopped, [see output for details](command:fuchsia.showOutput)'
);
return Promise.reject(exitCode);
}
}
);
});
}
/**
* Registers the `fuchsia.fx.ota` command.
*/
function registerFxoOta(setup: Setup, collection: FuchsiaDiagnostics) {
registerCommandWithAnalyticsEvent('fuchsia.fx.ota', () => {
void window.withProgress(
{
location: vscode.ProgressLocation.Notification,
title: 'fx ota',
cancellable: true,
},
async (progress, token) => {
const percentage = { value: 0 };
const cmd = setup.fx.runAsync(
['ota', '--build'],
(buffer) => handleData(buffer, 'fx ota', collection, progress, percentage),
(buffer) => handleData(buffer, 'fx ota', collection, progress, percentage)
);
token.onCancellationRequested(() => {
logger.info('fx ota stopped: user cancelled operation', 'fx ota');
cmd?.stop();
});
progress.report({ message: '[(details)](command:fuchsia.showOutput)' });
const exitCode = await cmd?.exitCode;
return exitCode === 0 ? Promise.resolve() : Promise.reject(exitCode);
}
);
});
}
/**
* Registers the `fuchsia.fx.ota.nobuild` command.
*/
function registerFxOtaNoBuild(setup: Setup, collection: FuchsiaDiagnostics) {
registerCommandWithAnalyticsEvent('fuchsia.fx.ota.nobuild', () => {
void window.withProgress(
{
location: vscode.ProgressLocation.Notification,
title: 'fx ota',
cancellable: true,
},
async (progress, token) => {
const percentage = { value: 0 };
const cmd = setup.fx.runAsync(
['ota', '--no-build'],
(buffer) => handleData(buffer, 'fx ota', collection, progress, percentage),
(buffer) => handleData(buffer, 'fx ota', collection, progress, percentage)
);
token.onCancellationRequested(() => {
logger.info('fx ota stopped: user cancelled operation', 'fx ota');
cmd?.stop();
});
progress.report({ message: '[(details)](command:fuchsia.showOutput)' });
const exitCode = await cmd?.exitCode;
return exitCode === 0 ? Promise.resolve() : Promise.reject(exitCode);
}
);
});
}
/**
* Registers fuchsia build commands
* @param ctx The vscode extension context.
* @param setup The setup object.
* @param collection The diagnostics collection.
*/
export function registerBuildCommands(
ctx: vscode.ExtensionContext,
setup: Setup,
collection: FuchsiaDiagnostics
) {
registerFxBuild(setup, collection);
registerFxoOta(setup, collection);
registerFxOtaNoBuild(setup, collection);
}