| // 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); |
| } |