2023-01-26 19:24:29 +01:00
|
|
|
import 'zx/globals';
|
|
|
|
import { SSH } from './ssh.js'
|
|
|
|
|
|
|
|
type DrvPath = string
|
|
|
|
type OutPath = string
|
|
|
|
type NixPath = DrvPath | OutPath
|
|
|
|
|
|
|
|
type ShownDerivation = {
|
|
|
|
args: Array<string>,
|
|
|
|
builder: string,
|
2023-03-01 11:40:56 +01:00
|
|
|
env: Record<string, string>,
|
|
|
|
inputDrvs: Record<DrvPath, string[]>,
|
2023-01-26 19:24:29 +01:00
|
|
|
inputSrcs: Array<OutPath>,
|
2023-03-01 11:40:56 +01:00
|
|
|
outputs: Record<string, { path: OutPath }>,
|
2023-01-26 19:24:29 +01:00
|
|
|
system: "x86-64_linux"
|
|
|
|
};
|
2023-03-01 11:40:56 +01:00
|
|
|
type ShowDerivationOutput = Record<DrvPath, ShownDerivation>
|
|
|
|
|
|
|
|
type OutputSpec<R> = { out: R } & Record<string, R>
|
|
|
|
|
|
|
|
type BuildOutput = {
|
|
|
|
drvPath: DrvPath,
|
|
|
|
outputs: OutputSpec<OutPath>,
|
|
|
|
startTime?: number,
|
|
|
|
stopTime?: number
|
2023-01-26 19:24:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
export class Expression {
|
|
|
|
expr: string
|
|
|
|
constructor(expr: string) {
|
|
|
|
this.expr = expr
|
|
|
|
}
|
|
|
|
async derive(): Promise<Derivation> {
|
2023-03-01 11:40:56 +01:00
|
|
|
const {drvPath} = await nix.derive(this.expr)
|
2023-01-26 19:24:29 +01:00
|
|
|
const drv = await nix.showDerivation(drvPath)
|
|
|
|
return new Derivation(drvPath, drv[drvPath])
|
|
|
|
}
|
2023-03-01 11:40:56 +01:00
|
|
|
async build(): Promise<OutputSpec<BuiltOutput>> {
|
2023-01-26 19:24:29 +01:00
|
|
|
const outputs = await nix.build(this.expr)
|
2023-03-01 11:40:56 +01:00
|
|
|
const drvMetaJson = await nix.showDerivation(outputs.drvPath)
|
2023-01-26 19:24:29 +01:00
|
|
|
const [drvPath, drvMeta] = Object.entries(drvMetaJson)[0]
|
|
|
|
const drv = new Derivation(drvPath, drvMeta)
|
2023-03-01 11:40:56 +01:00
|
|
|
const ret: Record<string, BuiltOutput> = {}
|
|
|
|
for (const [k, v] of Object.entries(outputs.outputs)) {
|
|
|
|
ret[k] = new BuiltOutput(v, drv)
|
|
|
|
}
|
|
|
|
return Object.assign(ret, { out: ret.out })
|
2023-01-26 19:24:29 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
export class Derivation {
|
|
|
|
path: string
|
|
|
|
//outputs?: Array<BuiltOutput>
|
|
|
|
meta: ShownDerivation
|
|
|
|
constructor(path: string, meta: ShownDerivation) {
|
|
|
|
this.path = path
|
|
|
|
this.meta = meta
|
|
|
|
}
|
|
|
|
async build(): Promise<{out: BuiltOutput} & Array<BuiltOutput>> {
|
2023-03-01 11:40:56 +01:00
|
|
|
const outputs: BuildOutput = await nix.build(this.path)
|
|
|
|
const ret = Object.values(outputs.outputs).map(x => new BuiltOutput(x, this))
|
2023-01-26 19:24:29 +01:00
|
|
|
return Object.assign(ret, { out: new BuiltOutput(this.meta.outputs.out.path, this) })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
export class BuiltOutput {
|
|
|
|
path: string
|
|
|
|
drv: Derivation
|
|
|
|
constructor(path: string, drv: Derivation) {
|
|
|
|
this.path = path
|
|
|
|
this.drv = drv
|
|
|
|
}
|
|
|
|
async copy(target: SSH): Promise<void> {
|
|
|
|
return nix.copy(this.path, target)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-01 11:40:56 +01:00
|
|
|
function dedupe<A, B, Rest extends any[]>(fn: (xs: A[], ...rest: Rest) => Promise<B[]>):
|
|
|
|
{ (x: A, ...rest: Rest): Promise<B>; (x: A[], ...rest: Rest): Promise<B[]>; } {
|
|
|
|
type QueueEntry = {
|
|
|
|
x: A,
|
|
|
|
resolve: (res: B) => void,
|
|
|
|
reject: (err: any) => void
|
|
|
|
}
|
|
|
|
const queues = new Map<string, QueueEntry[]>()
|
|
|
|
function inner(x: A, ...rest: Rest): Promise<B>
|
|
|
|
function inner(x: A[], ...rest: Rest): Promise<B[]>
|
|
|
|
function inner(x: A | A[], ...rest: Rest) {
|
|
|
|
// todo: also dedupe array results
|
|
|
|
if (Array.isArray(x)) return fn(x, ...rest)
|
|
|
|
// map queue by rest
|
|
|
|
const stringified = JSON.stringify(rest)
|
|
|
|
const had = queues.has(stringified)
|
|
|
|
const queue = queues.get(stringified) || []
|
|
|
|
const ret = new Promise<B>((resolve, reject) => {
|
|
|
|
queue.push({x, resolve, reject})
|
|
|
|
})
|
|
|
|
if (!had) {
|
|
|
|
queues.set(stringified, queue)
|
|
|
|
setImmediate(() => {
|
|
|
|
queues.delete(stringified)
|
|
|
|
fn(queue.map(({x}) => x), ...rest)
|
|
|
|
.then(results => {
|
|
|
|
for (const [i, {resolve}] of queue.entries()) resolve(results[i])
|
|
|
|
})
|
|
|
|
.catch(e => {
|
|
|
|
for (const {reject} of queue) reject(e)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
return inner
|
|
|
|
}
|
|
|
|
|
|
|
|
//function nixBuild(attr: string[]): Promise<BuildOutput[]>
|
|
|
|
//function nixBuild(attr: string): Promise<BuildOutput>
|
|
|
|
async function nixBuild(attr: string[]): Promise<BuildOutput[]> {
|
|
|
|
const tmp = (await $`mktemp -d`).stdout.trim()
|
|
|
|
process.on('exit', () => fs.removeSync(tmp))
|
|
|
|
const ret = JSON.parse((await $`nom build --json ${attr} -o ${tmp}/result`).stdout)
|
|
|
|
if (Array.isArray(attr)) return ret
|
|
|
|
return ret[0]
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace nix {
|
|
|
|
export const build = dedupe(nixBuild)
|
|
|
|
export async function showDerivation(path: NixPath): Promise<ShowDerivationOutput> {
|
2023-01-26 19:24:29 +01:00
|
|
|
return JSON.parse((await $`nix show-derivation ${path}`.quiet()).stdout)
|
2023-03-01 11:40:56 +01:00
|
|
|
}
|
|
|
|
export const derive = dedupe(async (attr: string[]): Promise<BuildOutput[]> => {
|
|
|
|
return JSON.parse((await $`nom build --json --dry-run ${attr}`).stdout)
|
|
|
|
})
|
|
|
|
export const copy = dedupe(async (attrs: string[], target: SSH): Promise<void[]> => {
|
2023-01-26 19:24:29 +01:00
|
|
|
process.env.NIX_SSHOPTS = "-o compression=no";
|
2024-03-10 14:37:51 +01:00
|
|
|
await $`nix copy ${attrs} -s --to ssh://${target.host}`
|
2023-03-01 11:40:56 +01:00
|
|
|
return Array(attrs.length)
|
|
|
|
})
|
2023-01-26 19:24:29 +01:00
|
|
|
}
|