blob: 37e59e761a96860babb532371fed45c48df531f0 [file] [log] [blame] [edit]
// Copyright 2022 The ChromiumOS Authors.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import * as path from 'path';
import * as vscode from 'vscode';
import * as commonUtil from '../common/common_util';
import {WrapFs} from '../common/cros';
import * as metrics from '../features/metrics/metrics';
import * as sudo from './sudo';
/**
* Chroot module detects the location of the chroot.
*
* In the following cases chroot cannot be detected:
* 1. There is no folder with ChromeOS source (a particular case is when
* a ChromeOS source file is opened, but not a folder).
* 2. Multiple chroots are detected - this happens in multiroot
* workspace. We show an error message and use one of the chroots.
*/
export function activate(context: vscode.ExtensionContext): ChrootService {
const service = new ChrootService(undefined, undefined);
context.subscriptions.push(
vscode.workspace.onDidChangeWorkspaceFolders(_e => {
service.onUpdate();
})
);
service.onUpdate();
return service;
}
/**
* Provides tools to operate chroot.
*/
export class ChrootService {
constructor(
private chrootFs: WrapFs<commonUtil.Chroot> | undefined,
private sourceFs: WrapFs<commonUtil.Source> | undefined
) {}
/**
* Returns an accessor to files under chroot. Don't store the returned value,
* as it might change when user opens different workspace.
*/
chroot(): WrapFs<commonUtil.Chroot> | undefined {
return this.chrootFs;
}
/**
* Returns an accessor to files under source. Don't store the returned value,
* as it might change when user opens different workspace.
*/
source(): WrapFs<commonUtil.Source> | undefined {
return this.sourceFs;
}
private setPaths(chroot?: commonUtil.Chroot) {
// Context for the custom `when` clause in boards and packages view.
void vscode.commands.executeCommand(
'setContext',
'cros-ide.chrootPath',
chroot
);
this.chrootFs = chroot ? new WrapFs(chroot) : undefined;
this.sourceFs = chroot
? new WrapFs(commonUtil.sourceDir(chroot))
: undefined;
}
/**
* Executes command in chroot. Returns InvalidPasswordError in case the user
* enters invalid password.
*/
async exec(
name: string,
args: string[],
options: sudo.SudoExecOptions
): ReturnType<typeof commonUtil.exec> {
const source = this.source();
if (source === undefined) {
return new Error(
'cros_sdk not found; open a directory under which chroot has been set up'
);
}
const crosSdk = path.join(source.root, 'chromite/bin/cros_sdk');
const crosSdkArgs = ['--', name, ...args];
return sudo.execSudo(crosSdk, crosSdkArgs, options);
}
onUpdate() {
const candidates = this.findChrootCandidates();
const currentChroot = this.chroot()?.root;
const selected: commonUtil.Chroot | undefined = candidates[0];
if (candidates.length > 1) {
// Do not await to avoid blocking activate().
void vscode.window.showErrorMessage(
'CrOS IDE does not support multiple chroots, ' +
`but found: [${candidates.join(', ')}]. Selecting ${selected}. ` +
'Open ChromeOS sources from at most one chroot per workspace to fix this problem.'
);
metrics.send({
category: 'background',
group: 'misc',
action: 'multiple chroot candidates',
});
}
// Make sure we don't change a defined chroot. This scenario happens
// when adding and removing folders under distinct chroots.
// (The first folder needs to be non-CrOS, because when it changes,
// then the extensions restart.)
if (currentChroot && currentChroot !== selected) {
void vscode.window.showErrorMessage(
`Chroot change ${currentChroot} → ${selected} will be ignored. ` +
'CrOS IDE requires reloading the window to change the chroot.'
);
metrics.send({
category: 'background',
group: 'misc',
action: 'chroot change rejected',
});
return;
}
// TODO(b:235773376): Make sure users of this service show a warning if invoked,
// when chroot is not available.
this.setPaths(selected);
}
private findChrootCandidates(): commonUtil.Chroot[] {
const candidates: commonUtil.Chroot[] = [];
for (const folder of vscode.workspace.workspaceFolders || []) {
const r = commonUtil.findChroot(folder.uri.fsPath);
if (r !== undefined && candidates.indexOf(r) === -1) {
candidates.push(r);
}
}
return candidates;
}
}