import { Arch, copyFile, log, orNullIfFileNotExist } from "builder-util"
import { Hash } from "crypto"
import { readJson, writeJson } from "fs-extra"
import { mkdir, readFile } from "fs/promises"
import * as path from "path"
export interface BuildCacheInfo {
executableDigest: string
}
export class BuildCacheManager {
static VERSION = "0"
readonly cacheDir: string
readonly cacheInfoFile: string
readonly cacheFile: string
cacheInfo: BuildCacheInfo | null = null
private newDigest: string | null = null
constructor(
outDir: string,
private readonly executableFile: string,
arch: Arch
) {
this.cacheDir = path.join(outDir, ".cache", Arch[arch])
this.cacheFile = path.join(this.cacheDir, "app.exe")
this.cacheInfoFile = path.join(this.cacheDir, "info.json")
}
async copyIfValid(digest: string): Promise<boolean> {
this.newDigest = digest
this.cacheInfo = await orNullIfFileNotExist(readJson(this.cacheInfoFile))
const oldDigest = this.cacheInfo == null ? null : this.cacheInfo.executableDigest
if (oldDigest !== digest) {
log.debug({ oldDigest, newDigest: digest }, "no valid cached executable found")
return false
}
log.debug({ cacheFile: this.cacheFile, file: this.executableFile }, `copying cached executable`)
try {
await copyFile(this.cacheFile, this.executableFile, false)
return true
} catch (e: any) {
if (e.code === "ENOENT" || e.code === "ENOTDIR") {
log.debug({ error: e.code }, "copy cached executable failed")
} else {
log.warn({ error: e.stack || e }, `cannot copy cached executable`)
}
}
return false
}
async save() {
if (this.newDigest == null) {
throw new Error("call copyIfValid before")
}
if (this.cacheInfo == null) {
this.cacheInfo = { executableDigest: this.newDigest }
} else {
this.cacheInfo.executableDigest = this.newDigest
}
try {
await mkdir(this.cacheDir, { recursive: true })
await Promise.all([writeJson(this.cacheInfoFile, this.cacheInfo), copyFile(this.executableFile, this.cacheFile, false)])
} catch (e: any) {
log.warn({ error: e.stack || e }, `cannot save build cache`)
}
}
}
export async function digest(hash: Hash, files: Array<string>) {
for (const content of await Promise.all(files.map(it => readFile(it)))) {
hash.update(content)
}
hash.update(BuildCacheManager.VERSION)
return hash.digest("base64")
}