Skip to content

Commit

Permalink
feat(nsis): display "Space Required" text for NSIS installer (#7531)
Browse files Browse the repository at this point in the history
  • Loading branch information
inickvel authored Apr 17, 2023
1 parent dab3aeb commit 0db9c66
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 13 deletions.
6 changes: 6 additions & 0 deletions .changeset/modern-waves-compete.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"app-builder-lib": minor
"builder-util": minor
---

Display "Space required" text for NSIS installer
4 changes: 4 additions & 0 deletions packages/app-builder-lib/src/targets/nsis/Defines.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ export type Defines = {
APP_ARM64_HASH?: string
APP_32_HASH?: string

APP_64_UNPACKED_SIZE?: string
APP_ARM64_UNPACKED_SIZE?: string
APP_32_UNPACKED_SIZE?: string

REQUEST_EXECUTION_LEVEL?: PortableOptions["requestExecutionLevel"]

UNPACK_DIR_NAME?: string | false
Expand Down
6 changes: 5 additions & 1 deletion packages/app-builder-lib/src/targets/nsis/NsisTarget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ export class NsisTarget extends Target {
defines.APP_BUILD_DIR = archs.get(archs.keys().next().value)
} else {
await BluebirdPromise.map(archs.keys(), async arch => {
const fileInfo = await this.packageHelper.packArch(arch, this)
const { fileInfo, unpackedSize } = await this.packageHelper.packArch(arch, this)
const file = fileInfo.path
const defineKey = arch === Arch.x64 ? "APP_64" : arch === Arch.arm64 ? "APP_ARM64" : "APP_32"
defines[defineKey] = file
Expand All @@ -240,6 +240,10 @@ export class NsisTarget extends Target {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
const defineHashKey = `${defineKey}_HASH` as "APP_64_HASH" | "APP_ARM64_HASH" | "APP_32_HASH"
defines[defineHashKey] = Buffer.from(fileInfo.sha512, "base64").toString("hex").toUpperCase()
// NSIS accepts size in KiloBytes and supports only whole numbers
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
const defineUnpackedSizeKey = `${defineKey}_UNPACKED_SIZE` as "APP_64_UNPACKED_SIZE" | "APP_ARM64_UNPACKED_SIZE" | "APP_32_UNPACKED_SIZE"
defines[defineUnpackedSizeKey] = Math.ceil(unpackedSize / 1024).toString()

if (this.isWebInstaller) {
await packager.dispatchArtifactCreated(file, this, arch)
Expand Down
30 changes: 21 additions & 9 deletions packages/app-builder-lib/src/targets/nsis/nsisUtil.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Arch, log } from "builder-util"
import { PackageFileInfo } from "builder-util-runtime"
import { getBinFromUrl, getBinFromCustomLoc } from "../../binDownload"
import { copyFile } from "builder-util/out/fs"
import { copyFile, dirSize } from "builder-util/out/fs"
import * as path from "path"
import { getTemplatePath } from "../../util/pathManager"
import { NsisTarget } from "./NsisTarget"
Expand Down Expand Up @@ -39,30 +39,42 @@ export const NSIS_PATH = () => {
})
}

export interface PackArchResult {
fileInfo: PackageFileInfo
unpackedSize: number
}

export class AppPackageHelper {
private readonly archToFileInfo = new Map<Arch, Promise<PackageFileInfo>>()
private readonly archToResult = new Map<Arch, Promise<PackArchResult>>()
private readonly infoToIsDelete = new Map<PackageFileInfo, boolean>()

/** @private */
refCount = 0

constructor(private readonly elevateHelper: CopyElevateHelper) {}

async packArch(arch: Arch, target: NsisTarget): Promise<PackageFileInfo> {
let infoPromise = this.archToFileInfo.get(arch)
if (infoPromise == null) {
async packArch(arch: Arch, target: NsisTarget): Promise<PackArchResult> {
let resultPromise = this.archToResult.get(arch)
if (resultPromise == null) {
const appOutDir = target.archs.get(arch)!
infoPromise = this.elevateHelper.copy(appOutDir, target).then(() => target.buildAppPackage(appOutDir, arch))
this.archToFileInfo.set(arch, infoPromise)
resultPromise = this.elevateHelper
.copy(appOutDir, target)
.then(() => target.buildAppPackage(appOutDir, arch))
.then(async fileInfo => ({
fileInfo,
unpackedSize: await dirSize(appOutDir),
}))
this.archToResult.set(arch, resultPromise)
}

const info = await infoPromise
const result = await resultPromise
const { fileInfo: info } = result
if (target.isWebInstaller) {
this.infoToIsDelete.set(info, false)
} else if (!this.infoToIsDelete.has(info)) {
this.infoToIsDelete.set(info, true)
}
return info
return result
}

async finishBuild(): Promise<any> {
Expand Down
31 changes: 29 additions & 2 deletions packages/app-builder-lib/templates/nsis/common.nsh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

BrandingText "${PRODUCT_NAME} ${VERSION}"
ShowInstDetails nevershow
SpaceTexts none
!ifdef BUILD_UNINSTALLER
ShowUninstDetails nevershow
!endif
Expand All @@ -13,6 +12,34 @@ Name "${PRODUCT_NAME}"
!define APP_EXECUTABLE_FILENAME "${PRODUCT_FILENAME}.exe"
!define UNINSTALL_FILENAME "Uninstall ${PRODUCT_FILENAME}.exe"

!macro setSpaceRequired SECTION_ID
!ifdef APP_64_UNPACKED_SIZE
!ifdef APP_32_UNPACKED_SIZE
!ifdef APP_ARM64_UNPACKED_SIZE
${if} ${IsNativeARM64}
SectionSetSize ${SECTION_ID} ${APP_ARM64_UNPACKED_SIZE}
${elseif} ${IsNativeAMD64}
SectionSetSize ${SECTION_ID} ${APP_64_UNPACKED_SIZE}
${else}
SectionSetSize ${SECTION_ID} ${APP_32_UNPACKED_SIZE}
${endif}
!else
${if} ${RunningX64}
SectionSetSize ${SECTION_ID} ${APP_64_UNPACKED_SIZE}
${else}
SectionSetSize ${SECTION_ID} ${APP_32_UNPACKED_SIZE}
${endif}
!endif
!else
SectionSetSize ${SECTION_ID} ${APP_64_UNPACKED_SIZE}
!endif
!else
!ifdef APP_32_UNPACKED_SIZE
SectionSetSize ${SECTION_ID} ${APP_32_UNPACKED_SIZE}
!endif
!endif
!macroend

!macro check64BitAndSetRegView
# https://github.com/electron-userland/electron-builder/issues/2420
${If} ${IsWin2000}
Expand Down Expand Up @@ -107,7 +134,7 @@ Name "${PRODUCT_NAME}"
LogSet ${SETTING}
!endif
!macroend

!define LogText "!insertmacro LogTextMacroEB"
!macro LogTextMacroEB INPUT_TEXT
!ifdef ENABLE_LOGGING_ELECTRON_BUILDER
Expand Down
8 changes: 7 additions & 1 deletion packages/app-builder-lib/templates/nsis/installer.nsi
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ Var oldMenuDirectory
!endif

Function .onInit
Call setInstallSectionSpaceRequired

SetOutPath $INSTDIR
${LogSet} on

Expand Down Expand Up @@ -82,7 +84,7 @@ FunctionEnd
!include "installUtil.nsh"
!endif

Section "install"
Section "install" INSTALL_SECTION_ID
!ifndef BUILD_UNINSTALLER
# If we're running a silent upgrade of a per-machine installation, elevate so extracting the new app will succeed.
# For a non-silent install, the elevation will be triggered when the install mode is selected in the UI,
Expand Down Expand Up @@ -114,6 +116,10 @@ Section "install"
!endif
SectionEnd

Function setInstallSectionSpaceRequired
!insertmacro setSpaceRequired ${INSTALL_SECTION_ID}
FunctionEnd

!ifdef BUILD_UNINSTALLER
!include "uninstaller.nsh"
!endif
21 changes: 21 additions & 0 deletions packages/builder-util/src/fs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,27 @@ export function copyDir(src: string, destination: string, options: CopyDirOption
}).then(() => BluebirdPromise.map(links, it => symlink(it.link, it.file, symlinkType), CONCURRENCY))
}

export async function dirSize(dirPath: string): Promise<number> {
const entries = await readdir(dirPath, { withFileTypes: true })

const entrySizes = entries.map(async entry => {
const entryPath = path.join(dirPath, entry.name)

if (entry.isDirectory()) {
return await dirSize(entryPath)
}

if (entry.isFile()) {
const { size } = await stat(entryPath)
return size
}

return 0
})

return (await Promise.all(entrySizes)).reduce((entrySize, totalSize) => entrySize + totalSize, 0)
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const DO_NOT_USE_HARD_LINKS = (file: string) => false
// eslint-disable-next-line @typescript-eslint/no-unused-vars
Expand Down

0 comments on commit 0db9c66

Please sign in to comment.