Skip to content

Commit

Permalink
feat(nsis): display required disk space
Browse files Browse the repository at this point in the history
  • Loading branch information
Nikolay Velizhanin authored and Nikolay Velizhanin committed Apr 10, 2023
1 parent f6944a3 commit cbcab51
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 53 deletions.
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
32 changes: 23 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,44 @@ 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(() => dirSize(appOutDir))
.then(unpackedSize =>
target.buildAppPackage(appOutDir, arch).then(fileInfo => ({
fileInfo,
unpackedSize,
}))
)
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
29 changes: 27 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,32 @@ 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
SectionSetSize ${SECTION_ID} ${APP_32_UNPACKED_SIZE}
!endif
!macroend

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

!define LogText "!insertmacro LogTextMacroEB"
!macro LogTextMacroEB INPUT_TEXT
!ifdef ENABLE_LOGGING_ELECTRON_BUILDER
Expand Down
84 changes: 43 additions & 41 deletions packages/app-builder-lib/templates/nsis/installer.nsi
Original file line number Diff line number Diff line change
Expand Up @@ -39,50 +39,11 @@ Var oldMenuDirectory
!insertmacro customHeader
!endif

Function .onInit
SetOutPath $INSTDIR
${LogSet} on

!ifmacrodef preInit
!insertmacro preInit
!endif

!ifdef DISPLAY_LANG_SELECTOR
!insertmacro MUI_LANGDLL_DISPLAY
!endif

!ifdef BUILD_UNINSTALLER
WriteUninstaller "${UNINSTALLER_OUT_FILE}"
!insertmacro quitSuccess
!else
!insertmacro check64BitAndSetRegView

!ifdef ONE_CLICK
!insertmacro ALLOW_ONLY_ONE_INSTALLER_INSTANCE
!else
${IfNot} ${UAC_IsInnerInstance}
!insertmacro ALLOW_ONLY_ONE_INSTALLER_INSTANCE
${EndIf}
!endif

!insertmacro initMultiUser

!ifmacrodef customInit
!insertmacro customInit
!endif

!ifmacrodef addLicenseFiles
InitPluginsDir
!insertmacro addLicenseFiles
!endif
!endif
FunctionEnd

!ifndef BUILD_UNINSTALLER
!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 @@ -116,4 +77,45 @@ SectionEnd

!ifdef BUILD_UNINSTALLER
!include "uninstaller.nsh"
!endif
!endif

Function .onInit
!insertmacro setSpaceRequired ${INSTALL_SECTION_ID}

SetOutPath $INSTDIR
${LogSet} on

!ifmacrodef preInit
!insertmacro preInit
!endif

!ifdef DISPLAY_LANG_SELECTOR
!insertmacro MUI_LANGDLL_DISPLAY
!endif

!ifdef BUILD_UNINSTALLER
WriteUninstaller "${UNINSTALLER_OUT_FILE}"
!insertmacro quitSuccess
!else
!insertmacro check64BitAndSetRegView

!ifdef ONE_CLICK
!insertmacro ALLOW_ONLY_ONE_INSTALLER_INSTANCE
!else
${IfNot} ${UAC_IsInnerInstance}
!insertmacro ALLOW_ONLY_ONE_INSTALLER_INSTANCE
${EndIf}
!endif

!insertmacro initMultiUser

!ifmacrodef customInit
!insertmacro customInit
!endif

!ifmacrodef addLicenseFiles
InitPluginsDir
!insertmacro addLicenseFiles
!endif
!endif
FunctionEnd
19 changes: 19 additions & 0 deletions packages/builder-util/src/fs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,25 @@ 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 cbcab51

Please sign in to comment.