perf(windows): overlap package and runtime preparation

This commit is contained in:
winewill committed 2026-10-20 19:14:15 +08:00
1 parent e7a9e2683d
commit 8c56ebbb3b
5 files changed
+128 -31

No files matched your search

+42 -26
View File
@@ -370,7 +370,7 @@ async function main(): Promise<void> {
version: buildVersion, productVersion, node: process.version,
commit: packaged.commit,
dirty: packaged.dirty,
}, { parallel: target.platform === 'darwin', secrets })
}, { parallel: true, secrets })
console.log(`DESKTOP_PACKAGING_RECORD ${run.directory}`)
const previousDirectory = process.env.DSH_DESKTOP_PACKAGING_RUN_DIR
process.env.DSH_DESKTOP_PACKAGING_RUN_DIR = run.directory
@@ -479,31 +479,47 @@ export async function packageTarget(
})
}
await execute(invocation.unsigned ? ['run', 'build', '--profile', 'local'] : ['run', 'build:official'], buildEnv, REPOSITORY_ROOT)
await execute([
...(invocation.unsigned
? ['exec', 'tsx', 'apps/desktop/scripts/pack-local-packages.ts']
: ['run', 'release:pack', '--family', 'dsh']),
'--out', buildPaths.packedDsh, ...packArguments,
], buildEnv, REPOSITORY_ROOT)
await execute([
'--dir',
'apps/desktop-host',
'pack',
'--pack-destination',
buildPaths.packedDsh,
], buildEnv, REPOSITORY_ROOT)
await execute(['run', 'release:pack', '--family', 'vendor', '--out', buildPaths.packedVendor, ...packArguments], buildEnv, REPOSITORY_ROOT)
rmSync(buildPaths.packedLandlock, { recursive: true, force: true })
mkdirSync(buildPaths.packedLandlock, { recursive: true })
await execute(['--dir', 'native/system', 'run', 'build:ts'], buildEnv, REPOSITORY_ROOT)
await execute([
'--dir',
'native/system/packages/entry',
'pack',
'--pack-destination',
buildPaths.packedLandlock,
], buildEnv, REPOSITORY_ROOT)
await execute(['run', 'prepare:runtime', ...(signPrimaryRuntime ? ['--defer-primary-runtime-smoke'] : [])], downloadEnv)
let preparationFailure: { error: unknown } | undefined
const executePreparation = async (args: readonly string[], env: NodeJS.ProcessEnv, cwd?: string): Promise<void> => {
if (preparationFailure !== undefined) throw preparationFailure.error
try { await execute(args, env, cwd) }
catch (error) { preparationFailure ??= { error }; throw error }
}
const pack = async (): Promise<void> => {
await executePreparation([
...(invocation.unsigned
? ['exec', 'tsx', 'apps/desktop/scripts/pack-local-packages.ts']
: ['run', 'release:pack', '--family', 'dsh']),
'--out', buildPaths.packedDsh, ...packArguments,
], buildEnv, REPOSITORY_ROOT)
await executePreparation([
'--dir',
'apps/desktop-host',
'pack',
'--pack-destination',
buildPaths.packedDsh,
], buildEnv, REPOSITORY_ROOT)
await executePreparation(['run', 'release:pack', '--family', 'vendor', '--out', buildPaths.packedVendor, ...packArguments], buildEnv, REPOSITORY_ROOT)
rmSync(buildPaths.packedLandlock, { recursive: true, force: true })
mkdirSync(buildPaths.packedLandlock, { recursive: true })
await executePreparation(['--dir', 'native/system', 'run', 'build:ts'], buildEnv, REPOSITORY_ROOT)
await executePreparation([
'--dir',
'native/system/packages/entry',
'pack',
'--pack-destination',
buildPaths.packedLandlock,
], buildEnv, REPOSITORY_ROOT)
}
const prepareRuntime = () => executePreparation(
['run', 'prepare:runtime', ...(signPrimaryRuntime ? ['--defer-primary-runtime-smoke'] : [])], downloadEnv)
if (target.platform === 'win32') {
const results = await Promise.allSettled([pack(), prepareRuntime()])
for (const result of results) if (result.status === 'rejected') throw result.reason
} else {
await pack()
await prepareRuntime()
}
if (signPrimaryRuntime) await execute(['run', 'sign:primary-runtime'], electronBuilderEnv)
await execute(['run', 'prepare:packages'], targetEnv)
await execute(['run', 'prepare:dsh', ...(signPrimaryRuntime ? ['--defer-runtime-smoke'] : [])], downloadEnv)
@@ -1,5 +1,6 @@
import { writeFileSync } from 'node:fs'
import { resolve } from 'node:path'
import { setImmediate } from 'node:timers/promises'
import { afterEach, expect, it, vi } from 'vitest'
import { packageTarget, parseDesktopPackageInvocation } from '../scripts/package-target.ts'
import { withMacOSNotarizationProxy } from '../scripts/macos-notarization-proxy.ts'
@@ -44,6 +45,83 @@ function supervisor(failure?: string) {
return { run, stages }
}
it.each(['pack', 'runtime'] as const)('overlaps Windows preparation and waits for both before signing when %s finishes first', async (first) => {
const { run, stages } = supervisor()
const pack = Promise.withResolvers<undefined>()
const runtime = Promise.withResolvers<undefined>()
const finished = new Set<string>()
run.run.mockImplementation(async (stage) => {
stages.push(stage)
if (stage.startsWith('run release:pack --family dsh ')) await pack.promise
if (stage === 'run prepare:runtime --defer-primary-runtime-smoke') {
await runtime.promise
finished.add('runtime')
}
if (stage.startsWith('--dir native/system/packages/entry pack ')) finished.add('pack')
if (stage === 'run sign:primary-runtime') expect([...finished].sort()).toEqual(['pack', 'runtime'])
})
const operation = packageTarget(parseDesktopPackageInvocation(['win-x64'], 'win32', 'x64'), environment, run)
const outcome = operation.then(() => undefined, (error: unknown) => error)
try {
await vi.waitFor(() => {
expect(stages.some(stage => stage.startsWith('run release:pack --family dsh '))).toBe(true)
expect(stages).toContain('run prepare:runtime --defer-primary-runtime-smoke')
})
expect(stages).not.toContain('run sign:primary-runtime')
;(first === 'pack' ? pack : runtime).resolve(undefined)
await vi.waitFor(() => { expect(finished.has(first)).toBe(true) })
expect(stages).not.toContain('run sign:primary-runtime')
expect(writeFileSync).not.toHaveBeenCalled()
;(first === 'pack' ? runtime : pack).resolve(undefined)
expect(await outcome).toBeUndefined()
expect(stages).toContain('run sign:primary-runtime')
expect(writeFileSync).toHaveBeenCalledOnce()
} finally {
pack.resolve(undefined)
runtime.resolve(undefined)
await outcome
}
})
it.each(['pack', 'runtime'] as const)('drains the other Windows preparation after %s fails without starting later packing or signing', async (failed) => {
const { run, stages } = supervisor()
const pack = Promise.withResolvers<undefined>()
const runtime = Promise.withResolvers<undefined>()
const failure = new Error(`${failed} preparation failed`)
let settled = false
run.run.mockImplementation(async (stage) => {
stages.push(stage)
if (stage.startsWith('run release:pack --family dsh ')) await pack.promise
if (stage === 'run prepare:runtime --defer-primary-runtime-smoke') await runtime.promise
})
const outcome = packageTarget(parseDesktopPackageInvocation(['win-x64'], 'win32', 'x64'), environment, run).then(
() => { settled = true; return undefined },
(error: unknown) => { settled = true; return error },
)
try {
await vi.waitFor(() => {
expect(stages.some(stage => stage.startsWith('run release:pack --family dsh '))).toBe(true)
expect(stages).toContain('run prepare:runtime --defer-primary-runtime-smoke')
})
;(failed === 'pack' ? pack : runtime).reject(failure)
await setImmediate()
expect(settled).toBe(false)
expect(writeFileSync).not.toHaveBeenCalled()
;(failed === 'pack' ? runtime : pack).resolve(undefined)
expect(await outcome).toBe(failure)
expect(stages.filter(stage => stage.startsWith('run release:pack'))).toHaveLength(1)
expect(stages.some(stage => stage.startsWith('--dir apps/desktop-host pack'))).toBe(false)
expect(stages.some(stage => stage.startsWith('--dir native/system'))).toBe(false)
expect(stages.some(stage => stage.startsWith('run sign:primary-runtime'))).toBe(false)
expect(stages.some(stage => stage.startsWith('run prepare:packages') || stage.startsWith('run prepare:dsh'))).toBe(false)
expect(writeFileSync).not.toHaveBeenCalled()
} finally {
pack.resolve(undefined)
runtime.resolve(undefined)
await outcome
}
})
it.each([undefined, '3'])('passes the package cache and selected compression to their subprocesses (%s)', async (level) => {
const { run } = supervisor()
const cache = resolve('fixture-package-cache')
@@ -162,7 +240,10 @@ it.each([
const invocation = parseDesktopPackageInvocation([target, '--unsigned'],
target === 'win-x64' ? 'win32' : 'darwin', target === 'win-x64' ? 'x64' : 'arm64')
await expect(packageTarget(invocation, environment, run)).rejects.toThrow('stage refused')
expect(stages.at(-1)?.startsWith(failure)).toBe(true)
const failedAt = stages.findIndex(stage => stage.startsWith(failure))
expect(failedAt).toBeGreaterThanOrEqual(0)
expect(stages.slice(failedAt + 1)).toEqual(
target === 'win-x64' && failure.startsWith('exec tsx') ? ['run prepare:runtime'] : [])
expect(stages.some(stage => stage.startsWith('--dir apps/desktop-host pack'))).toBe(false)
expect(writeFileSync).not.toHaveBeenCalled()
})
@@ -33,8 +33,8 @@
en: 5401b605a2f3a44e
zh: fc1f6ee667bcd20d
/windows-signing-and-runner-operations/signature-cache-and-concurrent-stages:
en: 78593443275fe259
zh: 9a8c1b541f479273
en: 6e8208d19ad510e5
zh: eee42e8938c0f603
/windows-signing-and-runner-operations/verification-limits:
en: 94a471410c84a1c7
zh: 115228e4ca36581d
+1 -1
View File
@@ -92,7 +92,7 @@ Cache-hit copying, hashing and per-file candidate trust checks run with `DSH_DES
PE discovery reads every regular file's header with at most eight open files, rejects links and malformed Windows executables, and drains active reads and closes before reporting failure. Packaging records `windows:enumerate-code` and `windows:inspect-signatures` as nested phases of signing and artifact verification; their timings are included in the enclosing stages.
Windows packaging reads `DSH_DESKTOP_WINDOWS_PACK_CONCURRENCY` from `.env.windows` or the selected preset: a positive integer, defaulting to `4`. Packaging batches first-party and vendor tarballs with bounded workers, then validates every payload and records publication order. Setting the limit to `1` selects serial packing; standalone `release:pack` defaults to `1`.
Windows packaging reads `DSH_DESKTOP_WINDOWS_PACK_CONCURRENCY` from `.env.windows` or the selected preset: a positive integer, defaulting to `4`. Packaging batches first-party and vendor tarballs with bounded workers, then validates every payload and records publication order. After the complete build succeeds, tarball packing overlaps Electron and primary-runtime preparation in separate output directories. Both must finish successfully before runtime signing; preparation failure stops new packing commands and drains active processes before returning. Setting the limit to `1` selects serial tarball workers; standalone `release:pack` defaults to `1`.
Each runtime stage writes `SIGNATURE_CACHE_SUMMARY` to stdout and its packaging journal, including the actual directory, policy identity, hit/miss counts, newly published and retained entries, signing requests, avoided signing requests and validation failures. Timings distinguish signing, trust verification of restored and newly signed files, and restoration; restoration includes its trust check and staging cleanup. These counters exclude preflight, final artifact signing and the outer runtime signature checks. Stage lock events record waiting separately. Concurrent timings sum per-file work rather than stage wall time.
+1 -1
View File
@@ -92,7 +92,7 @@ Windows 打包命令通过 `DESKTOP_PACKAGING_RECORD` 输出 `.desktop-build/pac
PE 发现会读取每个普通文件的头部,同时最多打开八个文件,拒绝链接和损坏的 Windows 可执行文件,并在报告失败前等待在途读取和关闭结束。打包将 `windows:enumerate-code` 与 `windows:inspect-signatures` 记录为签名和产物验证的嵌套阶段;其耗时已包含在外层阶段中。
Windows 打包从 `.env.windows` 或所选预设读取 `DSH_DESKTOP_WINDOWS_PACK_CONCURRENCY`:必须为正整数,默认 `4`。Windows 使用有界 worker 批量打包第一方和 vendor tarball,随后校验每个产物并记录发布顺序。设为 `1` 时串行打包;独立的 `release:pack` 默认使用 `1`。
Windows 打包从 `.env.windows` 或所选预设读取 `DSH_DESKTOP_WINDOWS_PACK_CONCURRENCY`:必须为正整数,默认 `4`。Windows 使用有界 worker 批量打包第一方和 vendor tarball,随后校验每个产物并记录发布顺序。完整构建成功后,tarball 打包与 Electron、主要运行时准备在各自输出目录中并行执行。两者都成功结束后才开始运行时签名;准备失败会停止新的打包命令,并等待在途进程结束后返回。设为 `1` 时 tarball worker 串行执行;独立的 `release:pack` 默认使用 `1`。
每个运行时阶段向标准输出及打包日志写入 `SIGNATURE_CACHE_SUMMARY`,包含实际目录、策略标识、命中及未命中数、新发布及保留项数、签名请求数、省去的签名请求数和验证失败数。计时区分签名、恢复文件和新签名文件的信任验证及恢复;恢复耗时包含其信任检查和暂存清理。这些统计不包含预检、最终产物签名及外层运行时验签。阶段锁事件单独记录等待时间。 并发计时累加逐文件工作耗时,不代表阶段墙钟时间。