-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
/
task.ts
226 lines (186 loc) · 6.16 KB
/
task.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
import chalk from "chalk"
import {BuildError} from "@compiler/error"
export {BuildError}
function join(items: string[], sep0: string, sep1: string): string {
if (items.length <= 1) {
return items.join("")
} else {
return `${items.slice(0, -1).join(sep0)}${sep1}${items[items.length-1]}`
}
}
export type Result<T = unknown> = Success<T> | Failure<T>
export class Success<T> {
constructor(readonly value: T) {}
is_Success(): this is Success<T> {
return true
}
is_Failure(): this is Failure<T> {
return false
}
}
export class Failure<T> {
constructor(readonly value: Error) {}
is_Success(): this is Success<T> {
return false
}
is_Failure(): this is Failure<T> {
return true
}
}
export function success<T>(value: T): Result<T> {
return new Success(value)
}
export function failure<T>(error: Error): Result<T> {
return new Failure<T>(error)
}
export function log(message: string): void {
const now = new Date().toTimeString().split(" ")[0]
console.log(`[${chalk.gray(now)}] ${message}`)
}
export function print(message: string): void {
for (const line of message.split("\n")) {
log(line)
}
}
export function show_failure(failure: Failure<unknown>): void {
show_error(failure.value)
}
export function show_error(error: unknown): void {
if (error instanceof BuildError) {
log(`${chalk.red("failed:")} ${error.message}`)
} else {
const msg = error instanceof Error && error.stack != null ? error.stack : error
print(`${chalk.red("failed:")} ${msg}`)
}
}
export type Fn<Args extends unknown[], T> = (...args: Args) => Promise<Result<T> | void>
export class Task<T = unknown> {
constructor(readonly name: string,
readonly deps: Dependency[],
readonly fn?: Fn<unknown[], T>) {}
toString(): string {
return this.name
}
}
const tasks = new Map<string, Task>()
export function task2<T> (name: string, deps: [], fn: () => Promise<Result<T>>): Task<T>
export function task2<T, T0> (name: string, deps: [Task<T0>], fn: (v0: T0) => Promise<Result<T>>): Task<T>
export function task2<T, T0, T1> (name: string, deps: [Task<T0>, Task<T1>], fn: (v0: T0, v1: T1) => Promise<Result<T>>): Task<T>
export function task2<T, T0, T1, T2>(name: string, deps: [Task<T0>, Task<T1>, Task<T2>], fn: (v0: T0, v1: T1, v2: T2) => Promise<Result<T>>): Task<T>
export function task2<T, Args extends unknown[]>(name: string, deps: Task<unknown>[], fn: (...args: Args) => Promise<Result<T>>): Task<T> {
return task(name, deps.map((dep) => dep.name), fn as Fn<unknown[], T>)
}
export type TaskLike = string | Task
export type DependencyLike = TaskLike | Dependency
function _resolve_dep(dep: DependencyLike): Dependency {
if (dep instanceof Dependency) {
return dep
} else {
return new Dependency(_resolve_task(dep))
}
}
function _resolve_task(dep: TaskLike): Task {
if (dep instanceof Task) {
return dep
} else {
const task = tasks.get(dep)
if (task != null) {
return task
} else {
throw new BuildError("tasks", `can't resolve ${dep} task`)
}
}
}
export function task<T>(name: string, deps: DependencyLike[] | Fn<unknown[], T>, fn?: Fn<unknown[], T>): Task<T> {
if (!Array.isArray(deps)) {
fn = deps
deps = []
}
const task = new Task<T>(name, deps.map(_resolve_dep), fn)
tasks.set(name, task)
return task
}
export function task_names(): string[] {
return Array.from(tasks.keys())
}
export function passthrough(dep: TaskLike): Dependency {
return new Dependency(_resolve_task(dep), true)
}
class Dependency {
constructor(readonly task: Task, readonly passthrough: boolean = false) {}
}
export async function run(task: Task): Promise<Result> {
const finished = new Map<Task, Result>()
const failures: Task[] = []
async function exec_task(task: Task): Promise<Result> {
if (task.fn == null) {
log(`Finished '${chalk.cyan(task.name)}'`)
return success(undefined)
} else {
log(`Starting '${chalk.cyan(task.name)}'...`)
const args = []
for (const dep of task.deps) {
const result = finished.get(dep.task)
if (result != null && result.is_Success()) {
args.push(result.value)
} else if (dep.passthrough) {
args.push(undefined)
} else {
throw new Error(`${dep} value is not available for ${task}`)
}
}
const start = Date.now()
let result: Result
try {
const value = await task.fn(...args)
result = typeof value == "undefined" ? success(undefined) : value
} catch (error) {
result = failure(error instanceof Error ? error : new Error(`${error}`))
}
const end = Date.now()
const diff = end - start
const duration = diff >= 1000 ? `${(diff / 1000).toFixed(2)} s` : `${diff} ms`
log(`Finished '${chalk.cyan(task.name)}' after ${chalk.magenta(duration)}`)
return result
}
}
function fail(failures: Task[]): Result {
const names = join(failures.map((dep_task) => `'${chalk.cyan(dep_task.name)}'`), ", ", " and ")
return failure(new BuildError(task.name, `task '${chalk.cyan(task.name)}' failed because ${names} failed`))
}
async function _run(task: Task, passthrough: boolean): Promise<Result> {
if (finished.has(task)) {
return finished.get(task)!
} else {
const failed_deps = []
for (const dep of task.deps) {
const result = await _run(dep.task, dep.passthrough)
if (result.is_Failure()) {
show_failure(result)
failed_deps.push(dep.task)
}
}
let result: Result
if (failed_deps.length == 0) {
result = await exec_task(task)
} else {
result = fail(failed_deps)
}
if (result.is_Failure()) {
show_failure(result)
if (passthrough) {
result = success(undefined)
failures.push(task)
}
}
finished.set(task, result)
return result
}
}
const result = await _run(task, false)
if (result.is_Success() && failures.length != 0) {
return fail(failures)
} else {
return result
}
}