blob: bb1ec034d362822cf65135bf574f2fae10ab1085 [file] [log] [blame]
import * as vscode from 'vscode';
import * as path from 'path';
import * as fs from 'fs';
export enum FormatToolErrorCause {
ToolNotSpecified, ToolNotExecutable, FormatterError
}
export class FormatToolError extends Error {
cause: FormatToolErrorCause;
formatterMessage: string | undefined;
constructor(cause: FormatToolErrorCause) {
super();
this.cause = cause;
}
}
/**
* Finds the Zircon build output directory given a Fuchsia repository root.
*
* This function derives the correct filesystem path, but it doesn't check to
* ensure that the path exists. Callers are responsible for ensuring that the
* resulting path exists and has the expected contents.
*
* @param fuchsiaRoot the filesystem path to the root of the Fuchsia
* repository.
* @returns the filesystem path to the root of the Zircon build output
* directory.
*/
function deriveZirconOutPath(fuchsiaRoot: string): string | null {
const buildOutputPointer = path.join(fuchsiaRoot, '.fx-build-dir');
try {
const outputRoot = fs.readFileSync(buildOutputPointer, { encoding: 'utf8' }).trim();
return path.join(fuchsiaRoot, outputRoot + '.zircon');
} catch (err) {
// TODO: proper error handling
return null;
}
}
/**
* Finds the fidl-format tool given a Zircon output repository root.
*
* This function derives the correct filesystem path, but it doesn't check to
* ensure that the path exists. Callers are responsible for ensuring that the
* resulting path exists and has the expected contents.
*
* @param zirconRoot the filesystem path to the root of the Fuchsia
* repository.
* @returns the filesystem path to the fidl-format tool.
*/
function deriveFidlFormatPath(zirconRoot: string): string | null {
return path.join(zirconRoot, 'tools', 'fidl-format');
}
/**
* Attempts to find the fidl-format tool in the current directory, returning
* the path to the tool if it's present and executable by the current user.
*
* @param folderPath The path to the folder to search in.
* @returns The path to the fidl-format tool in folderPath or null if the tool can't be found or isn't executable.
*/
function findFidlFormatInFolder(folderPath: string): string | null {
const zirconRoot = deriveZirconOutPath(folderPath);
if (!zirconRoot) {
return null;
}
const toolPath = deriveFidlFormatPath(zirconRoot);
if (!toolPath) {
return null;
}
try {
fs.accessSync(toolPath, fs.constants.X_OK);
} catch (err) {
return null;
}
return toolPath;
}
/**
* Attempts to find the fidl-format tool by asking VS Code to find the root of
* the Fuchsia repository.
*
* @returns the absolute path to fidl-format or null if it couldn't be found.
*/
async function guessFidlFormatPathFromWorkspace(): Promise<string | null> {
const uris = await vscode.workspace.findFiles('.fx-build-dir', null, 1);
if (uris.length < 1) {
return null;
}
for (const uri of uris) {
if (uri.scheme !== 'file') {
// If the repository isn't local, fidl-format won't be runnable
// even if present.
continue;
}
const basePath = path.dirname(uri.fsPath);
const toolPath = findFidlFormatInFolder(basePath);
if (toolPath === null) {
continue;
}
return toolPath;
}
return null;
}
/**
* Attempts to find the fidl-format tool by traversing the directory tree up
* from the root of each workspace folder toward the root of the filesystem.
*
* @returns the absolute path to fidl-format or null if it couldn't be found.
*/
function guessFidlFormatPathFromAncestors(): string | null {
const folders = vscode.workspace.workspaceFolders;
if (!folders) {
return null;
}
for (const folder of folders) {
if (folder.uri.scheme !== 'file') {
// If the repository isn't local, fidl-format won't be runnable
// even if present.
continue;
}
let folderPath;
// Traverse through parent repositories until the direct child of the
// root directory.
for (folderPath = folder.uri.fsPath;
path.dirname(folderPath) !== folderPath;
folderPath = path.dirname(folderPath)) {
const toolPath = findFidlFormatInFolder(folderPath);
if (toolPath) {
return toolPath;
}
}
// Also check the root directory.
const toolPath = findFidlFormatInFolder(folderPath);
if (toolPath) {
return toolPath;
}
}
return null;
}
/**
* Gets the fidl-format tool path as configured in VS Code preferences.
*
* @returns the path to fidl-format as configured by the user, or `null` if the
* tool isn't set.
*/
export function getFidlFormatToolPathFromPreferences(): string | null {
let formatTool: string | undefined | null
= vscode.workspace.getConfiguration('fidl').get('formatTool');
if (formatTool) {
try {
fs.accessSync(formatTool, fs.constants.X_OK);
} catch (err) {
throw new FormatToolError(FormatToolErrorCause.ToolNotExecutable);
}
return formatTool;
} else {
return null;
}
}
/**
* Checks whether the path to fidl-format has been filled out. If the default
* value of an empty string is found, an error is shown as a notification.
*/
export async function guessFidlFormatToolPath(): Promise<string> {
let formatTool: string | undefined | null
= getFidlFormatToolPathFromPreferences();
// Guessing by ancestor is faster than searching a large workspace for the
// Fuchsia root, so we try this approach first.
if (!formatTool) {
formatTool = guessFidlFormatPathFromAncestors();
}
if (!formatTool) {
formatTool = await guessFidlFormatPathFromWorkspace();
}
if (!formatTool) {
throw new FormatToolError(FormatToolErrorCause.ToolNotSpecified);
}
try {
// Throws if tool is not present or not executable.
fs.accessSync(formatTool, fs.constants.X_OK);
} catch (err) {
throw new FormatToolError(FormatToolErrorCause.ToolNotExecutable);
}
return formatTool;
}