build-plan.mjs 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. import { Arch } from 'builder-util'
  2. import path from 'node:path'
  3. import { PLATFORM_LABELS, formatDuration, logBanner, logPlatform } from './build-log.mjs'
  4. export const PLATFORM_ORDER = ['mac', 'win', 'linux']
  5. function resolvePlatformName(name) {
  6. const map = {
  7. darwin: 'mac',
  8. linux: 'linux',
  9. mac: 'mac',
  10. win: 'win',
  11. win32: 'win',
  12. windows: 'win',
  13. }
  14. return map[name] || null
  15. }
  16. function formatArch(arch) {
  17. if (arch == null) {
  18. return 'unknown'
  19. }
  20. return Arch[arch] || String(arch)
  21. }
  22. export function getBuildPlan(makeFor, targetPlatformsConfigs) {
  23. if (makeFor === 'dev') {
  24. return [{ platform: 'mac', targets: targetPlatformsConfigs.mac.mac }]
  25. }
  26. if (makeFor === 'mac') {
  27. return [{ platform: 'mac', targets: targetPlatformsConfigs.mac.mac }]
  28. }
  29. if (makeFor === 'win') {
  30. return [{ platform: 'win', targets: targetPlatformsConfigs.win.win }]
  31. }
  32. if (makeFor === 'linux') {
  33. return [{ platform: 'linux', targets: targetPlatformsConfigs.linux.linux }]
  34. }
  35. return PLATFORM_ORDER.map((platform) => ({
  36. platform,
  37. targets: targetPlatformsConfigs.all[platform],
  38. }))
  39. }
  40. export function createBuildTracker({ plan, compression, macBuildState, winBuildState, artifactBuildCompletedHook }) {
  41. // Track platform timing through electron-builder hooks while the outer loop
  42. // runs one platform build at a time for cleaner, non-interleaved logging.
  43. const stats = new Map(
  44. plan.map(({ platform, targets }) => [
  45. platform,
  46. {
  47. targets,
  48. startedAt: 0,
  49. finishedAt: 0,
  50. },
  51. ]),
  52. )
  53. function getStat(platform) {
  54. if (!stats.has(platform)) {
  55. stats.set(platform, {
  56. targets: [],
  57. startedAt: 0,
  58. finishedAt: 0,
  59. })
  60. }
  61. return stats.get(platform)
  62. }
  63. function markStarted(platform) {
  64. const stat = getStat(platform)
  65. if (!stat.startedAt) {
  66. stat.startedAt = Date.now()
  67. logBanner(`Build ${PLATFORM_LABELS[platform]}`)
  68. logPlatform(platform, `targets: ${stat.targets.join(', ')}`)
  69. logPlatform(platform, `compression: ${compression}`)
  70. if (platform === 'mac') {
  71. logPlatform(platform, `code signing: ${macBuildState.sign ? 'enabled' : 'disabled'}`)
  72. logPlatform(platform, `notarization: ${macBuildState.notarize ? 'enabled' : 'disabled'}`)
  73. } else if (platform === 'win') {
  74. logPlatform(platform, `code signing: ${winBuildState.sign ? 'enabled' : 'disabled'}`)
  75. } else {
  76. logPlatform(platform, 'notarization: disabled')
  77. }
  78. }
  79. return stat
  80. }
  81. function markFinished(platform) {
  82. const stat = getStat(platform)
  83. stat.finishedAt = Date.now()
  84. return stat
  85. }
  86. return {
  87. hooks: {
  88. beforePack(context) {
  89. const platform = resolvePlatformName(context.electronPlatformName)
  90. if (!platform) {
  91. return
  92. }
  93. markStarted(platform)
  94. // beforePack fires for each arch-specific app bundle preparation.
  95. logPlatform(platform, `packaging app bundle for ${formatArch(context.arch)}...`)
  96. },
  97. afterPack(context) {
  98. const platform = resolvePlatformName(context.electronPlatformName)
  99. if (!platform) {
  100. return
  101. }
  102. markFinished(platform)
  103. logPlatform(platform, `app bundle ready for ${formatArch(context.arch)}`)
  104. },
  105. async artifactBuildCompleted(context) {
  106. const platform = resolvePlatformName(context.packager?.platform?.name)
  107. if (platform) {
  108. markStarted(platform)
  109. }
  110. // Reuse the DMG notarization hook from the packaging config so logging and
  111. // timing stay in one place while the notarization logic remains isolated.
  112. const artifactFile = context.file || ''
  113. const isMacDmg = platform === 'mac' && path.extname(artifactFile) === '.dmg'
  114. if (isMacDmg && !macBuildState.notarize) {
  115. logPlatform(platform, `skipping dmg notarization: ${path.basename(artifactFile)}`)
  116. } else {
  117. await artifactBuildCompletedHook(context)
  118. }
  119. if (!platform) {
  120. return
  121. }
  122. markFinished(platform)
  123. const targetName = context.target?.name || path.extname(artifactFile).slice(1)
  124. logPlatform(platform, `artifact ready (${targetName}): ${path.basename(artifactFile)}`)
  125. },
  126. },
  127. printSummary() {
  128. logBanner('Build Summary')
  129. for (const { platform } of plan) {
  130. const stat = getStat(platform)
  131. const elapsed = stat.startedAt && stat.finishedAt ? stat.finishedAt - stat.startedAt : 0
  132. logPlatform(platform, `elapsed: ${elapsed > 0 ? formatDuration(elapsed) : 'n/a'}`)
  133. }
  134. },
  135. }
  136. }