Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(nsis): display "Space required" text for NSIS installer #7531

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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