| // 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 fs from 'fs'; |
| import * as path from 'path'; |
| import {Chroot, CrosOut} from './common_util'; |
| |
| // Wraps functions in fs or fs.promises, adding prefix to given paths. |
| export class WrapFs<T extends string> { |
| constructor(readonly root: T) {} |
| |
| realpath(p: string): string { |
| if (p.startsWith(this.root)) { |
| return p; |
| } |
| return path.join(this.root, p); |
| } |
| |
| async copyFile(p: string, dest: string): Promise<void> { |
| return fs.promises.copyFile(this.realpath(p), dest); |
| } |
| |
| async stat(p: string): Promise<fs.Stats> { |
| return fs.promises.stat(this.realpath(p)); |
| } |
| |
| existsSync(p: string): boolean { |
| return fs.existsSync(this.realpath(p)); |
| } |
| |
| async readdir(p: string): Promise<string[]> { |
| return fs.promises.readdir(this.realpath(p)); |
| } |
| |
| async rm(p: string, opts?: {force?: boolean}): Promise<void> { |
| return fs.promises.rm(this.realpath(p), opts); |
| } |
| |
| watchSync( |
| p: string, |
| listener: (eventType: string, fileName: string | null) => void |
| ): void { |
| fs.watch(this.realpath(p), listener); |
| } |
| } |
| |
| /** |
| * @returns Boards that have been set up, ordered by access time (newest to |
| * oldest). |
| */ |
| export async function getSetupBoardsRecentFirst( |
| chroot: WrapFs<Chroot>, |
| out: WrapFs<CrosOut> |
| ): Promise<string[]> { |
| return getSetupBoardsOrdered( |
| chroot, |
| out, |
| async (fs, dir) => fs.stat(dir), |
| (a, b) => b.atimeMs - a.atimeMs |
| ); |
| } |
| |
| /** |
| * @returns Boards that have been set up in alphabetic order. |
| */ |
| export async function getSetupBoardsAlphabetic( |
| chroot: WrapFs<Chroot>, |
| out: WrapFs<CrosOut> |
| ): Promise<string[]> { |
| return getSetupBoardsOrdered( |
| chroot, |
| out, |
| async (_fs, dir) => dir, |
| (a, b) => a.localeCompare(b) |
| ); |
| } |
| |
| async function getSetupBoardsOrdered<T>( |
| chroot: WrapFs<Chroot>, |
| out: WrapFs<CrosOut>, |
| keyFn: (fs: WrapFs<Chroot | CrosOut>, dir: string) => Promise<T>, |
| compareFn: (a: T, b: T) => number |
| ): Promise<string[]> { |
| const res = []; |
| for (const fs of [chroot, out]) { |
| res.push(...(await getSetupBoardsOrderedInner(fs, keyFn, compareFn))); |
| } |
| return res; |
| } |
| |
| async function getSetupBoardsOrderedInner<T, F extends Chroot | CrosOut>( |
| fs: WrapFs<F>, |
| keyFn: (fs: WrapFs<F>, dir: string) => Promise<T>, |
| compareFn: (a: T, b: T) => number |
| ): Promise<string[]> { |
| const build = '/build'; |
| |
| // /build does not exist outside chroot, which causes problems in tests. |
| if (!fs.existsSync(build)) { |
| return []; |
| } |
| |
| const dirs = await fs.readdir(build); |
| const dirStat: Array<[string, T]> = []; |
| for (const dir of dirs) { |
| // README file exists in chroot/build if it's a directory on which out/build |
| // is mounted in chroot. |
| if (dir === 'bin' || dir === 'README') { |
| continue; |
| } |
| dirStat.push([dir, await keyFn(fs, path.join(build, dir))]); |
| } |
| dirStat.sort(([, a], [, b]) => compareFn(a, b)); |
| return dirStat.map(([x]) => x); |
| } |