From 62688bcefc5ac63b42842adb44250cbfb8960036 Mon Sep 17 00:00:00 2001 From: Tyler Butler Date: Thu, 26 Sep 2024 18:14:14 -0700 Subject: [PATCH 01/16] Squished branch: bt-input-output-task --- .../packages/build-tools/src/fluidBuild/tasks/taskFactory.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/build-tools/packages/build-tools/src/fluidBuild/tasks/taskFactory.ts b/build-tools/packages/build-tools/src/fluidBuild/tasks/taskFactory.ts index 23523163b733..73557d3154b2 100644 --- a/build-tools/packages/build-tools/src/fluidBuild/tasks/taskFactory.ts +++ b/build-tools/packages/build-tools/src/fluidBuild/tasks/taskFactory.ts @@ -159,12 +159,15 @@ export class TaskFactory { return new GroupTask(node, command, [subTask], taskName); } - // Leaf task + // Leaf tasks; first try to map the executable to a known task type const executable = getExecutableFromCommand(command).toLowerCase(); const ctor = executableToLeafTask[executable]; if (ctor) { return new ctor(node, command, taskName); } + + + return new UnknownLeafTask(node, command, taskName); } From 71aeb382cbc6dd2b0f2e00fb2583aa13d99322ce Mon Sep 17 00:00:00 2001 From: Tyler Butler Date: Fri, 27 Sep 2024 11:47:46 -0700 Subject: [PATCH 02/16] feat(fluid-build): Add support for declarative tasks --- .vscode/launch.json | 3 +- build-tools/packages/build-tools/package.json | 1 + .../build-tools/src/common/gitRepo.ts | 26 + .../packages/build-tools/src/common/utils.ts | 13 +- .../src/fluidBuild/buildContext.ts | 8 + .../build-tools/src/fluidBuild/buildGraph.ts | 31 +- .../build-tools/src/fluidBuild/fluidBuild.ts | 9 +- .../src/fluidBuild/fluidBuildConfig.ts | 22 + .../src/fluidBuild/fluidRepoBuild.ts | 14 +- .../src/fluidBuild/tasks/groupTask.ts | 4 +- .../fluidBuild/tasks/leaf/declarativeTask.ts | 67 + .../src/fluidBuild/tasks/leaf/leafTask.ts | 33 +- .../src/fluidBuild/tasks/leaf/miscTasks.ts | 10 +- .../src/fluidBuild/tasks/leaf/prettierTask.ts | 10 +- .../src/fluidBuild/tasks/leaf/tscTask.ts | 8 +- .../build-tools/src/fluidBuild/tasks/task.ts | 12 + .../src/fluidBuild/tasks/taskFactory.ts | 66 +- .../src/fluidBuild/tasks/taskHandlers.ts | 36 + build-tools/pnpm-lock.yaml | 52 +- fluidBuild.config.cjs | 21 +- package.json | 4 +- pnpm-lock.yaml | 4112 +++-------------- 22 files changed, 893 insertions(+), 3669 deletions(-) create mode 100644 build-tools/packages/build-tools/src/fluidBuild/buildContext.ts create mode 100644 build-tools/packages/build-tools/src/fluidBuild/tasks/leaf/declarativeTask.ts create mode 100644 build-tools/packages/build-tools/src/fluidBuild/tasks/taskHandlers.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index 38dfc5953f02..fcb6c3976ee7 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -41,7 +41,8 @@ { "name": "fluid-build", "program": "${workspaceFolder}/build-tools/packages/build-tools/bin/fluid-build", - "cwd": "${workspaceFolder}/packages/common/container-definitions", + // "cwd": "${workspaceFolder}/packages/common/container-definitions", + "cwd": "${workspaceFolder}/build-tools", "request": "launch", "skipFiles": ["/**"], "type": "node", diff --git a/build-tools/packages/build-tools/package.json b/build-tools/packages/build-tools/package.json index 94fa215362f9..8f376dcf4b59 100644 --- a/build-tools/packages/build-tools/package.json +++ b/build-tools/packages/build-tools/package.json @@ -50,6 +50,7 @@ "find-up": "^7.0.0", "fs-extra": "^11.2.0", "glob": "^7.2.3", + "globby": "^11.1.0", "ignore": "^5.2.4", "json5": "^2.2.3", "lodash": "^4.17.21", diff --git a/build-tools/packages/build-tools/src/common/gitRepo.ts b/build-tools/packages/build-tools/src/common/gitRepo.ts index e245499686b5..4c00c870396a 100644 --- a/build-tools/packages/build-tools/src/common/gitRepo.ts +++ b/build-tools/packages/build-tools/src/common/gitRepo.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. */ +import { execFileSync } from "node:child_process"; import { parseISO } from "date-fns"; import registerDebug from "debug"; import { exec, execNoError } from "./utils"; @@ -311,3 +312,28 @@ export class GitRepo { return execNoError(`git ${command}`, this.resolvedRoot, pipeStdIn); } } + +/** + * Finds the root directory of a Git repository synchronously. + * + * This function uses `git rev-parse --show-toplevel` command to find the top-level directory + * of the current Git repository. It executes the command synchronously using `child_process.execFileSync`. + * If the current directory is not part of a Git repository, it throws an error. + * + * @param cwd - The current working directory from which to start searching for a Git repository root. + * @returns The path to the root directory of the Git repository. + * @throws Error If the current directory is not part of a Git repository. + */ +export function findGitRootSync(cwd: string = process.cwd()): string { + try { + const rootPath = execFileSync("git", ["rev-parse", "--show-toplevel"], { + cwd, + encoding: "utf8", + }).trim(); + return rootPath; + } catch (error) { + throw new Error( + `Failed to find Git repository root. Make sure you are inside a Git repository. Error: ${error}`, + ); + } +} diff --git a/build-tools/packages/build-tools/src/common/utils.ts b/build-tools/packages/build-tools/src/common/utils.ts index 406ce42a6dcc..2d2381239480 100644 --- a/build-tools/packages/build-tools/src/common/utils.ts +++ b/build-tools/packages/build-tools/src/common/utils.ts @@ -9,14 +9,19 @@ import * as path from "path"; import isEqual from "lodash.isequal"; /** - * An array of commands that are known to have subcommands and should be parsed as such + * An array of commands that are known to have subcommands and should be parsed as such. These will be combined with + * any additional commands provided in the fluid build config. */ -const multiCommandExecutables = ["flub", "biome"]; +const defaultMultiCommandExecutables = ["flub", "biome"]; -export function getExecutableFromCommand(command: string) { +export function getExecutableFromCommand(command: string, multiCommandExecutables: string[]) { let toReturn: string; const commands = command.split(" "); - if (multiCommandExecutables.includes(commands[0])) { + const multiExecutables: Set = new Set([ + ...defaultMultiCommandExecutables, + ...multiCommandExecutables, + ]); + if (multiExecutables.has(commands[0])) { // For multi-commands (e.g., "flub bump ...") our heuristic is to scan for the first argument that cannot // be the name of a sub-command, such as '.' or an argument that starts with '-'. // diff --git a/build-tools/packages/build-tools/src/fluidBuild/buildContext.ts b/build-tools/packages/build-tools/src/fluidBuild/buildContext.ts new file mode 100644 index 000000000000..18cbaba34e1e --- /dev/null +++ b/build-tools/packages/build-tools/src/fluidBuild/buildContext.ts @@ -0,0 +1,8 @@ +import type { GitRepo } from "../common/gitRepo"; +import type { IFluidBuildConfig } from "./fluidBuildConfig"; + +export interface BuildContext { + fluidBuildConfig: IFluidBuildConfig | undefined; + repoRoot: string; + gitRepo: GitRepo; +} diff --git a/build-tools/packages/build-tools/src/fluidBuild/buildGraph.ts b/build-tools/packages/build-tools/src/fluidBuild/buildGraph.ts index 22f2622a878a..f6c29bf98406 100644 --- a/build-tools/packages/build-tools/src/fluidBuild/buildGraph.ts +++ b/build-tools/packages/build-tools/src/fluidBuild/buildGraph.ts @@ -12,6 +12,7 @@ import registerDebug from "debug"; import { defaultLogger } from "../common/logging"; import { Package } from "../common/npmPackage"; import { Timer } from "../common/timer"; +import type { BuildContext } from "./buildContext"; import { FileHashCache } from "./fileHashCache"; import { TaskDefinition, @@ -60,12 +61,13 @@ class TaskStats { public leafQueueWaitTimeTotal = 0; } -class BuildContext { +class BuildContextInternal { public readonly fileHashCache = new FileHashCache(); public readonly taskStats = new TaskStats(); public readonly failedTaskLines: string[] = []; constructor( public readonly repoPackageMap: Map, + public readonly context: BuildContext, public readonly workerPool?: WorkerPool, ) {} } @@ -84,7 +86,7 @@ export class BuildPackage { private readonly _taskDefinitions: TaskDefinitions; constructor( - public readonly buildContext: BuildContext, + public readonly buildContext: BuildContextInternal, public readonly pkg: Package, globalTaskDefinitions: TaskDefinitions, ) { @@ -156,7 +158,7 @@ export class BuildPackage { private createTask(taskName: string, pendingInitDep: Task[]) { const config = this.getTaskDefinition(taskName); if (config?.script === false) { - const task = TaskFactory.CreateTargetTask(this, taskName); + const task = TaskFactory.CreateTargetTask(this, this.buildContext.context, taskName); pendingInitDep.push(task); return task; } @@ -169,7 +171,13 @@ export class BuildPackage { // Find the script task (without the lifecycle task) let scriptTask = this.scriptTasks.get(taskName); if (scriptTask === undefined) { - scriptTask = TaskFactory.Create(this, command, pendingInitDep, taskName); + scriptTask = TaskFactory.Create( + this, + command, + this.buildContext.context, + pendingInitDep, + taskName, + ); pendingInitDep.push(scriptTask); this.scriptTasks.set(taskName, scriptTask); } @@ -179,6 +187,7 @@ export class BuildPackage { // script task will depend on this instance instead of the standalone script task without the lifecycle. const task = TaskFactory.CreateTaskWithLifeCycle( this, + this.buildContext.context, scriptTask, this.ensureScriptTask(`pre${taskName}`, pendingInitDep), this.ensureScriptTask(`post${taskName}`, pendingInitDep), @@ -207,7 +216,13 @@ export class BuildPackage { throw new Error(`${this.pkg.nameColored}: '${taskName}' must be a script task`); } - const task = TaskFactory.Create(this, command, pendingInitDep, taskName); + const task = TaskFactory.Create( + this, + command, + this.buildContext.context, + pendingInitDep, + taskName, + ); pendingInitDep.push(task); return task; } @@ -474,17 +489,19 @@ export class BuildPackage { export class BuildGraph { private matchedPackages = 0; private readonly buildPackages = new Map(); - private readonly buildContext; + private readonly buildContext: BuildContextInternal; public constructor( packages: Map, releaseGroupPackages: Package[], + context: BuildContext, private readonly buildTaskNames: string[], globalTaskDefinitions: TaskDefinitionsOnDisk | undefined, getDepFilter: (pkg: Package) => (dep: Package) => boolean, ) { - this.buildContext = new BuildContext( + this.buildContext = new BuildContextInternal( packages, + context, options.worker ? new WorkerPool(options.workerThreads, options.workerMemoryLimit) : undefined, diff --git a/build-tools/packages/build-tools/src/fluidBuild/fluidBuild.ts b/build-tools/packages/build-tools/src/fluidBuild/fluidBuild.ts index fc94e9b7401f..25c9b5d18107 100644 --- a/build-tools/packages/build-tools/src/fluidBuild/fluidBuild.ts +++ b/build-tools/packages/build-tools/src/fluidBuild/fluidBuild.ts @@ -5,12 +5,13 @@ import chalk from "chalk"; +import { GitRepo } from "../common/gitRepo"; import { defaultLogger } from "../common/logging"; import { Timer } from "../common/timer"; import { BuildGraph, BuildResult } from "./buildGraph"; import { commonOptions } from "./commonOptions"; import { FluidRepoBuild } from "./fluidRepoBuild"; -import { getResolvedFluidRoot } from "./fluidUtils"; +import { getFluidBuildConfig, getResolvedFluidRoot } from "./fluidUtils"; import { options, parseOptions } from "./options"; const { log, errorLog: error, warning: warn } = defaultLogger; @@ -24,7 +25,11 @@ async function main() { log(`Build Root: ${resolvedRoot}`); // Load the package - const repo = FluidRepoBuild.create(resolvedRoot); + const repo = FluidRepoBuild.create({ + repoRoot: resolvedRoot, + gitRepo: new GitRepo(resolvedRoot), + fluidBuildConfig: getFluidBuildConfig(resolvedRoot), + }); timer.time("Package scan completed"); // Set matched package based on options filter diff --git a/build-tools/packages/build-tools/src/fluidBuild/fluidBuildConfig.ts b/build-tools/packages/build-tools/src/fluidBuild/fluidBuildConfig.ts index d53c86e66ff5..7dbbae0df876 100644 --- a/build-tools/packages/build-tools/src/fluidBuild/fluidBuildConfig.ts +++ b/build-tools/packages/build-tools/src/fluidBuild/fluidBuildConfig.ts @@ -31,6 +31,19 @@ export interface IFluidBuildConfig { */ tasks?: TaskDefinitionsOnDisk; + /** + * Add task handlers based on configuration only. This allows you to add incremental build support for executables and + * commands that don't support it. + */ + declarativeTasks?: DeclarativeTasks; + + /** + * An array of commands that are known to have subcommands and should be parsed as such. + * + * These values will be combined with the default values: ["flub", "biome"] + */ + multiCommandExecutables?: string[]; + /** * A mapping of package or release group names to metadata about the package or release group. This can only be * configured in the repo-wide Fluid build config (the repo-root package.json). @@ -67,3 +80,12 @@ export type IFluidBuildDirEntry = string | IFluidBuildDir | (string | IFluidBuil export interface IFluidBuildDirs { [name: string]: IFluidBuildDirEntry; } + +export interface DeclarativeTasks { + [executable: string]: DeclarativeTask; +} + +export interface DeclarativeTask { + inputGlobs: string[]; + outputGlobs: string[]; +} diff --git a/build-tools/packages/build-tools/src/fluidBuild/fluidRepoBuild.ts b/build-tools/packages/build-tools/src/fluidBuild/fluidRepoBuild.ts index afbb14fc203d..18d06365ebed 100644 --- a/build-tools/packages/build-tools/src/fluidBuild/fluidRepoBuild.ts +++ b/build-tools/packages/build-tools/src/fluidBuild/fluidRepoBuild.ts @@ -12,6 +12,7 @@ import { defaultLogger } from "../common/logging"; import { MonoRepo } from "../common/monoRepo"; import { Package, Packages } from "../common/npmPackage"; import { ExecAsyncResult, isSameFileOrDir, lookUpDirSync } from "../common/utils"; +import type { BuildContext } from "./buildContext"; import { BuildGraph } from "./buildGraph"; import type { IFluidBuildDirs } from "./fluidBuildConfig"; import { FluidRepo } from "./fluidRepo"; @@ -32,16 +33,20 @@ export interface IPackageMatchedOptions { } export class FluidRepoBuild extends FluidRepo { - public static create(resolvedRoot: string) { + public static create(context: BuildContext) { // Default to just resolveRoot if no config is found - const packageManifest = getFluidBuildConfig(resolvedRoot) ?? { + const packageManifest = context.fluidBuildConfig ?? { repoPackages: { root: "", }, }; - return new FluidRepoBuild(resolvedRoot, packageManifest.repoPackages); + return new FluidRepoBuild(context.repoRoot, context, packageManifest.repoPackages); } - private constructor(resolvedRoot: string, repoPackages?: IFluidBuildDirs) { + private constructor( + resolvedRoot: string, + protected context: BuildContext, + repoPackages?: IFluidBuildDirs, + ) { super(resolvedRoot, repoPackages); } @@ -145,6 +150,7 @@ export class FluidRepoBuild extends FluidRepo { return new BuildGraph( this.createPackageMap(), this.getReleaseGroupPackages(), + this.context, buildTargetNames, getFluidBuildConfig(this.resolvedRoot)?.tasks, (pkg: Package) => { diff --git a/build-tools/packages/build-tools/src/fluidBuild/tasks/groupTask.ts b/build-tools/packages/build-tools/src/fluidBuild/tasks/groupTask.ts index 52e457aaaf5d..9f898238b34d 100644 --- a/build-tools/packages/build-tools/src/fluidBuild/tasks/groupTask.ts +++ b/build-tools/packages/build-tools/src/fluidBuild/tasks/groupTask.ts @@ -5,6 +5,7 @@ import { AsyncPriorityQueue } from "async"; +import type { BuildContext } from "../buildContext"; import { BuildPackage, BuildResult } from "../buildGraph"; import { LeafTask } from "./leaf/leafTask"; import { Task, TaskExec } from "./task"; @@ -13,11 +14,12 @@ export class GroupTask extends Task { constructor( node: BuildPackage, command: string, + context: BuildContext, protected readonly subTasks: Task[], taskName: string | undefined, private readonly sequential: boolean = false, ) { - super(node, command, taskName); + super(node, command, context, taskName); } public initializeDependentLeafTasks() { diff --git a/build-tools/packages/build-tools/src/fluidBuild/tasks/leaf/declarativeTask.ts b/build-tools/packages/build-tools/src/fluidBuild/tasks/leaf/declarativeTask.ts new file mode 100644 index 000000000000..774ff397f560 --- /dev/null +++ b/build-tools/packages/build-tools/src/fluidBuild/tasks/leaf/declarativeTask.ts @@ -0,0 +1,67 @@ +import globby from "globby"; + +import type { BuildContext } from "../../buildContext"; +import type { BuildPackage } from "../../buildGraph"; +import type { DeclarativeTask } from "../../fluidBuildConfig"; +import type { TaskHandlerFunction } from "../taskHandlers"; +import { LeafTask, LeafWithFileStatDoneFileTask } from "./leafTask"; + +export class InputOutputTask extends LeafWithFileStatDoneFileTask { + constructor( + node: BuildPackage, + command: string, + context: BuildContext, + taskName: string | undefined, + private readonly taskDefinition: DeclarativeTask, + ) { + super(node, command, context, taskName); + } + + /** + * Use hashes instead of modified times in donefile. + */ + protected get useHashes(): boolean { + return true; + } + + protected async getInputFiles(): Promise { + const { inputGlobs } = this.taskDefinition; + const inputFiles = await globby(inputGlobs, { + cwd: this.node.pkg.directory, + // file paths returned from getInputFiles and getOutputFiles should always be absolute + absolute: true, + // Ignore gitignored files + gitignore: true, + }); + return inputFiles; + } + + protected async getOutputFiles(): Promise { + const { outputGlobs } = this.taskDefinition; + const outputFiles = await globby(outputGlobs, { + cwd: this.node.pkg.directory, + // file paths returned from getInputFiles and getOutputFiles should always be absolute + absolute: true, + // Output files are often gitignored, so we don't want to exclude them like we do for input files + gitignore: false, + }); + return outputFiles; + } +} + +export function createDeclarativeTaskHandler( + // node: BuildPackage, + // command: string, + taskDefinition: DeclarativeTask, + // taskName?: string, +): TaskHandlerFunction { + const handler: TaskHandlerFunction = ( + node: BuildPackage, + command: string, + context: BuildContext, + taskName?: string, + ): LeafTask => { + return new InputOutputTask(node, command, context, taskName, taskDefinition); + }; + return handler; +} diff --git a/build-tools/packages/build-tools/src/fluidBuild/tasks/leaf/leafTask.ts b/build-tools/packages/build-tools/src/fluidBuild/tasks/leaf/leafTask.ts index e06bc5a2303f..1d53bdb0f1ec 100644 --- a/build-tools/packages/build-tools/src/fluidBuild/tasks/leaf/leafTask.ts +++ b/build-tools/packages/build-tools/src/fluidBuild/tasks/leaf/leafTask.ts @@ -14,6 +14,7 @@ import { existsSync } from "node:fs"; import { readFile, stat, unlink, writeFile } from "node:fs/promises"; import { defaultLogger } from "../../../common/logging"; import { ExecAsyncResult, execAsync, getExecutableFromCommand } from "../../../common/utils"; +import type { BuildContext } from "../../buildContext"; import { BuildPackage, BuildResult, summarizeBuildResult } from "../../buildGraph"; import { options } from "../../options"; import { Task, TaskExec } from "../task"; @@ -47,10 +48,11 @@ export abstract class LeafTask extends Task { constructor( node: BuildPackage, command: string, + context: BuildContext, taskName: string | undefined, private readonly isTemp: boolean = false, // indicate if the task is for temporary and not for execution. ) { - super(node, command, taskName); + super(node, command, context, taskName); if (!this.isDisabled) { this.node.buildContext.taskStats.leafTotalCount++; } @@ -151,7 +153,10 @@ export abstract class LeafTask extends Task { } public get executable() { - return getExecutableFromCommand(this.command); + return getExecutableFromCommand( + this.command, + this.context.fluidBuildConfig?.multiCommandExecutables ?? [], + ); } protected get useWorker() { @@ -530,8 +535,13 @@ export abstract class LeafWithDoneFileTask extends LeafTask { } export class UnknownLeafTask extends LeafTask { - constructor(node: BuildPackage, command: string, taskName: string | undefined) { - super(node, command, taskName); + constructor( + node: BuildPackage, + command: string, + context: BuildContext, + taskName: string | undefined, + ) { + super(node, command, context, taskName); } protected get isIncremental() { @@ -621,6 +631,11 @@ export abstract class LeafWithFileStatDoneFileTask extends LeafWithDoneFileTask const dstHashesP = Promise.all(dstFiles.map(mapHash)); const [srcHashes, dstHashes] = await Promise.all([srcHashesP, dstHashesP]); + + // sort by name for determinism + srcHashes.sort(sortByName); + dstHashes.sort(sortByName); + const output = JSON.stringify({ srcHashes, dstHashes, @@ -633,3 +648,13 @@ export abstract class LeafWithFileStatDoneFileTask extends LeafWithDoneFileTask } } } + +function sortByName(a: { name: string }, b: { name: string }): number { + if (a.name < b.name) { + return -1; + } + if (a.name > b.name) { + return 1; + } + return 0; +} diff --git a/build-tools/packages/build-tools/src/fluidBuild/tasks/leaf/miscTasks.ts b/build-tools/packages/build-tools/src/fluidBuild/tasks/leaf/miscTasks.ts index 17267e0af801..fa5e427e7363 100644 --- a/build-tools/packages/build-tools/src/fluidBuild/tasks/leaf/miscTasks.ts +++ b/build-tools/packages/build-tools/src/fluidBuild/tasks/leaf/miscTasks.ts @@ -8,6 +8,7 @@ import * as path from "node:path"; import picomatch from "picomatch"; import { getTypeTestPreviousPackageDetails } from "../../../common/typeTests"; +import type { BuildContext } from "../../buildContext"; import { BuildPackage } from "../../buildGraph"; import { globFn, toPosixPath } from "../taskUtils"; import { LeafTask, LeafWithFileStatDoneFileTask } from "./leafTask"; @@ -73,8 +74,13 @@ export class CopyfilesTask extends LeafWithFileStatDoneFileTask { private readonly flat: boolean = false; private readonly copyDstArg: string = ""; - constructor(node: BuildPackage, command: string, taskName: string | undefined) { - super(node, command, taskName); + constructor( + node: BuildPackage, + command: string, + context: BuildContext, + taskName: string | undefined, + ) { + super(node, command, context, taskName); // TODO: something better const args = this.command.split(" "); diff --git a/build-tools/packages/build-tools/src/fluidBuild/tasks/leaf/prettierTask.ts b/build-tools/packages/build-tools/src/fluidBuild/tasks/leaf/prettierTask.ts index f9a8cbb1e395..c3b6f73a9ce1 100644 --- a/build-tools/packages/build-tools/src/fluidBuild/tasks/leaf/prettierTask.ts +++ b/build-tools/packages/build-tools/src/fluidBuild/tasks/leaf/prettierTask.ts @@ -8,6 +8,7 @@ import { readFile, stat } from "node:fs/promises"; import * as path from "node:path"; import ignore from "ignore"; +import type { BuildContext } from "../../buildContext"; import { BuildPackage } from "../../buildGraph"; import { getInstalledPackageVersion, getRecursiveFiles, globFn } from "../taskUtils"; import { LeafWithDoneFileTask } from "./leafTask"; @@ -16,8 +17,13 @@ export class PrettierTask extends LeafWithDoneFileTask { private parsed: boolean = false; private entries: string[] = []; private ignorePath: string | undefined; - constructor(node: BuildPackage, command: string, taskName: string | undefined) { - super(node, command, taskName); + constructor( + node: BuildPackage, + command: string, + context: BuildContext, + taskName: string | undefined, + ) { + super(node, command, context, taskName); // TODO: something better const args = this.command.split(" "); diff --git a/build-tools/packages/build-tools/src/fluidBuild/tasks/leaf/tscTask.ts b/build-tools/packages/build-tools/src/fluidBuild/tasks/leaf/tscTask.ts index 043a5c4b120c..c120a884a0a2 100644 --- a/build-tools/packages/build-tools/src/fluidBuild/tasks/leaf/tscTask.ts +++ b/build-tools/packages/build-tools/src/fluidBuild/tasks/leaf/tscTask.ts @@ -79,7 +79,13 @@ export class TscTask extends LeafTask { continue; } checkedProjects.add(dir); - const tempTscTask = new TscTask(this.node, `tsc -p ${dir}`, undefined, true); + const tempTscTask = new TscTask( + this.node, + `tsc -p ${dir}`, + this.context, + undefined, + true, + ); if (!(await tempTscTask.checkTscIsUpToDate(checkedProjects))) { this.traceTrigger(`project reference ${dir} is not up to date`); return false; diff --git a/build-tools/packages/build-tools/src/fluidBuild/tasks/task.ts b/build-tools/packages/build-tools/src/fluidBuild/tasks/task.ts index 0c61ce663d5e..18e0b592bb2a 100644 --- a/build-tools/packages/build-tools/src/fluidBuild/tasks/task.ts +++ b/build-tools/packages/build-tools/src/fluidBuild/tasks/task.ts @@ -7,7 +7,11 @@ import { AsyncPriorityQueue, priorityQueue } from "async"; import * as assert from "assert"; import registerDebug from "debug"; +import { findGitRootSync } from "../../common/gitRepo"; +import type { BuildContext } from "../buildContext"; import { BuildPackage, BuildResult } from "../buildGraph"; +import type { IFluidBuildConfig } from "../fluidBuildConfig"; +import { getFluidBuildConfig } from "../fluidUtils"; import { options } from "../options"; import { LeafTask } from "./leaf/leafTask"; @@ -47,9 +51,13 @@ export abstract class Task { public get nameColored() { return `${this.node.pkg.nameColored}#${this.taskName ?? `<${this.command}>`}`; } + + public readonly fluidBuildConfig: IFluidBuildConfig; + protected constructor( protected readonly node: BuildPackage, public readonly command: string, + protected readonly context: BuildContext, public readonly taskName: string | undefined, ) { traceTaskInit(`${this.nameColored}`); @@ -57,6 +65,10 @@ export abstract class Task { // initializeDependentTasks won't be called for unnamed tasks this.dependentTasks = []; } + + // Load the fluidBuild config + const root = findGitRootSync(); + this.fluidBuildConfig = getFluidBuildConfig(root); } public get package() { diff --git a/build-tools/packages/build-tools/src/fluidBuild/tasks/taskFactory.ts b/build-tools/packages/build-tools/src/fluidBuild/tasks/taskFactory.ts index 73557d3154b2..ce20552da444 100644 --- a/build-tools/packages/build-tools/src/fluidBuild/tasks/taskFactory.ts +++ b/build-tools/packages/build-tools/src/fluidBuild/tasks/taskFactory.ts @@ -3,14 +3,18 @@ * Licensed under the MIT License. */ +import { findGitRootSync } from "../../common/gitRepo"; import { getExecutableFromCommand } from "../../common/utils"; +import type { BuildContext } from "../buildContext"; import { BuildPackage } from "../buildGraph"; +import { getFluidBuildConfig } from "../fluidUtils"; import { GroupTask } from "./groupTask"; import { ApiExtractorTask } from "./leaf/apiExtractorTask"; import { BiomeTask } from "./leaf/biomeTasks"; +import { createDeclarativeTaskHandler } from "./leaf/declarativeTask"; import { FlubCheckLayerTask, FlubCheckPolicyTask, FlubListTask } from "./leaf/flubTasks"; import { GenerateEntrypointsTask } from "./leaf/generateEntrypointsTask.js"; -import { LeafTask, UnknownLeafTask } from "./leaf/leafTask"; +import { UnknownLeafTask } from "./leaf/leafTask"; import { EsLintTask, TsLintTask } from "./leaf/lintTasks"; import { CopyfilesTask, @@ -27,10 +31,11 @@ import { Ts2EsmTask } from "./leaf/ts2EsmTask"; import { TscMultiTask, TscTask } from "./leaf/tscTask"; import { WebpackTask } from "./leaf/webpackTask"; import { Task } from "./task"; +import { type TaskHandler, isConstructorFunction } from "./taskHandlers"; // Map of executable name to LeafTasks const executableToLeafTask: { - [key: string]: new (node: BuildPackage, command: string, taskName?: string) => LeafTask; + [key: string]: TaskHandler; } = { "ts2esm": Ts2EsmTask, "tsc": TscTask, @@ -71,6 +76,21 @@ const executableToLeafTask: { "flub rename-types": RenameTypesTask, }; +function getTaskForExecutable(executable: string): TaskHandler | undefined { + const found = executableToLeafTask[executable]; + if (found === undefined) { + const gitRoot = findGitRootSync(); + const config = getFluidBuildConfig(gitRoot); + const declarativeTasks = config.declarativeTasks; + const taskMatch = declarativeTasks?.[executable]; + if (taskMatch !== undefined) { + return createDeclarativeTaskHandler(taskMatch); + } + } + + return found; +} + /** * Regular expression to parse `concurrently` arguments that specify package scripts. * The format is `npm: