[build scripts] upgrading NSIS; using native compiler builds for all platforms

GitOrigin-RevId: 2d5f3e243d1b58d6b9b27c96ada9f59bce8e4f63
This commit is contained in:
Roman Shevchenko
2025-06-05 17:20:51 +02:00
committed by intellij-monorepo-bot
parent ad7dc482f3
commit 3b8e9b6693
3 changed files with 128 additions and 119 deletions

View File

@@ -5,7 +5,7 @@ gradleApiVersion=8.14
gradleTAPI.jacksonCoreVersion=2.17.2
jdkBuild=17.0.15b1381.5
launcherBuild=252.16037
nsisBuild=1
nsisBuild=3.11
restarterBuild=243.6005
runtimeBuild=21.0.7b1021.38
snapDockerImage=registry.jetbrains.team/p/ij/docker-hub/jetbrains/snapcraft@sha256:0059496b7a941e27937bc523621a336bddc1327de8dcd03f6b0bd0b57892ce43

View File

@@ -1,11 +1,19 @@
### Upgrading NSIS to a newer version
The NSIS.zip archive contains both Windows and Linux binaries, so in order to upgrade NSIS, one has to download the former,
compile the latter, and pack both together. **Note**: please make sure all archives are of the same version.
The NSIS.zip archive contains binaries for all supported platforms, so to upgrade NSIS, one has to download packages,
compile the binaries, get required plugins, and pack everything together.
##### [Building](https://documentation.help/NSIS/SectionG.3.html) NSIS compiler on Linux
##### Downloading
1. Install [SCons](https://scons.org) (standalone package is enough).
1. Go to the [download site](https://sourceforge.net/projects/nsis/files/) and download
`nsis-VERSION.zip`, `nsis-VERSION-strlen_8192.zip`, and `nsis-VERSION-src.tar.bz2` archives.
2. Unpack the `nsis-VERSION.zip` archive and rename the top-level directory to 'NSIS'.
3. Delete unneeded stuff (`Docs`, `Examples`, `makensisw.exe`, `NSIS.*`, `Plugins/x86-ansi`).
4. Unpack the "strlen_8192" archive into the 'NSIS' directory (overwrite existing files).
##### [Building](https://documentation.help/NSIS/SectionG.3.html) NSIS compiler on Linux/macOS
1. Install [SCons](https://scons.org) (standalone package is enough) and build dependencies (gcc/g++, zlib-dev).
2. Unpack NSIS source archive and `cd` into that directory.
3. Build:
```
@@ -13,21 +21,24 @@ compile the latter, and pack both together. **Note**: please make sure all archi
SKIPSTUBS=all SKIPPLUGINS=all SKIPUTILS=all SKIPMISC=all NSIS_CONFIG_CONST_DATA_PATH=no NSIS_MAX_STRLEN=8192 PREFIX=. \
install-compiler
```
The resulting binary ('makensis') is in the current directory.
The resulting binary (`makensis`) is in the current directory; rename them according to the platform conventions
(`makensis-(mac|linux)-(amd64|aarch64)`).
##### Preparing the combined archive
1. [Download](https://sourceforge.net/projects/nsis/files/) and unpack zipped NSIS installation.
2. Rename NSIS top-level directory into 'NSIS' and drop unneeded stuff ('Docs', 'Examples', 'makensisw.exe', 'NSIS.*', 'Plugins/x86-ansi').
3. Download "strlen_8192" archive and unpack it into the 'NSIS' directory (overwrite existing files).
4. Copy compiled Linux binary into 'NSIS/Bin' directory.
5. From the old NSIS.zip archive, copy the following files into corresponding subdirectories of the 'NSIS' directory:
- 'Include/UAC.nsh'
- 'Plugins/x86-unicode/AccessControl.dll'
- 'Plugins/x86-unicode/ExecDos.dll'
- 'Plugins/x86-unicode/INetC.dll'
- 'Plugins/x86-unicode/ShellLink.dll'
- 'Plugins/x86-unicode/UAC.dll'
6. Zip the 'NSIS' directory.
7. Upload to https://jetbrains.team/p/ij/packages/files/intellij-build-dependencies/org/jetbrains/intellij/deps/nsis/.
8. Update the version of 'nsisBuild' in community/build/dependencies/dependencies.properties.
1. Copy compiled binaries into `NSIS/Bin` directory.
2. From the old NSIS.zip archive, copy the following plugin files into corresponding subdirectories of the 'NSIS' directory:
- `Include/UAC.nsh`
- `Plugins/x86-unicode/AccessControl.dll`
- `Plugins/x86-unicode/ExecDos.dll`
- `Plugins/x86-unicode/ShellLink.dll`
- `Plugins/x86-unicode/UAC.dll`
3. Zip the 'NSIS' directory.
4. Upload to https://jetbrains.team/p/ij/packages/files/intellij-build-dependencies/org/jetbrains/intellij/deps/nsis/.
5. Update the version of 'nsisBuild' in community/build/dependencies/dependencies.properties.
Plugin pages; for reference:
- [UAC](https://nsis.sourceforge.io/UAC_plug-in)
- [Access Control](https://nsis.sourceforge.io/AccessControl_plug-in)
- [ExecDos](https://nsis.sourceforge.io/ExecDos_plug-in)
- [ShellLink](https://nsis.sourceforge.io/ShellLink_plug-in)

View File

@@ -1,16 +1,19 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.intellij.build.impl
import com.intellij.openapi.util.SystemInfoRt
import com.intellij.openapi.util.io.FileUtilRt
import com.intellij.openapi.util.io.NioFiles
import com.intellij.util.io.Decompressor
import io.opentelemetry.api.trace.Span
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.jetbrains.intellij.build.*
import org.jetbrains.intellij.build.io.copyDir
import org.jetbrains.intellij.build.io.deleteDir
import org.jetbrains.intellij.build.BuildContext
import org.jetbrains.intellij.build.BuildOptions
import org.jetbrains.intellij.build.JvmArchitecture
import org.jetbrains.intellij.build.OsFamily
import org.jetbrains.intellij.build.WindowsDistributionCustomizer
import org.jetbrains.intellij.build.downloadFileToCacheLocation
import org.jetbrains.intellij.build.executeStep
import org.jetbrains.intellij.build.io.runProcess
import org.jetbrains.intellij.build.telemetry.TraceManager.spanBuilder
import org.jetbrains.intellij.build.telemetry.use
@@ -19,37 +22,40 @@ import java.nio.file.Path
import java.nio.file.StandardCopyOption
import java.nio.file.attribute.FileTime
import java.util.concurrent.TimeUnit
import kotlin.io.path.isDirectory
import kotlin.io.path.isRegularFile
import kotlin.io.path.setLastModifiedTime
import kotlin.time.Duration.Companion.hours
@Suppress("SpellCheckingInspection")
internal suspend fun buildNsisInstaller(winDistPath: Path,
additionalDirectoryToInclude: Path,
suffix: String,
customizer: WindowsDistributionCustomizer,
runtimeDir: Path,
context: BuildContext): Path? {
if (SystemInfoRt.isMac && !Docker.isAvailable) {
Span.current().addEvent("Windows installer cannot be built on macOS without Docker")
return null
}
internal suspend fun buildNsisInstaller(
winDistPath: Path,
additionalDirectoryToInclude: Path,
suffix: String,
customizer: WindowsDistributionCustomizer,
runtimeDir: Path,
context: BuildContext
): Path? {
val communityHome = context.paths.communityHomeDir
val outFileName = context.productProperties.getBaseArtifactName(context) + suffix
Span.current().setAttribute(outFileName, outFileName)
val box = context.paths.tempDir.resolve("winInstaller$suffix")
//noinspection SpellCheckingInspection
val nsiConfDir = box.resolve("nsiconf")
withContext(Dispatchers.IO) {
Files.createDirectories(nsiConfDir)
copyDir(context.paths.communityHomeDir.resolve("build/conf/nsis"), nsiConfDir)
val box = context.paths.tempDir.resolve("winInstaller${suffix}")
if (!SystemInfoRt.isWindows) {
val (nsisDir, nsisBin) = prepareNsis(context, box)
val nsiConfDir = box.resolve("nsi-conf")
Files.createDirectories(nsiConfDir)
NioFiles.copyRecursively(context.paths.communityHomeDir.resolve("build/conf/nsis"), nsiConfDir)
if (OsFamily.currentOs != OsFamily.WINDOWS) {
val ideaNsiPath = nsiConfDir.resolve("idea.nsi")
Files.writeString(ideaNsiPath, BuildUtils.replaceAll(text = Files.readString(ideaNsiPath),
replacements = mapOf("\${IMAGES_LOCATION}\\" to "\${IMAGES_LOCATION}/"),
marker = ""))
Files.writeString(ideaNsiPath, BuildUtils.replaceAll(
text = Files.readString(ideaNsiPath),
replacements = mapOf($$"${IMAGES_LOCATION}\\" to $$"${IMAGES_LOCATION}/"),
marker = "")
)
}
val generator = NsisFileListGenerator()
@@ -60,7 +66,7 @@ internal suspend fun buildNsisInstaller(winDistPath: Path,
generator.generateInstallerFile(nsiConfDir.resolve("idea_win.nsh"))
generator.generateUninstallerFile(nsiConfDir.resolve("unidea_win.nsh"))
prepareConfigurationFiles(nsiConfDir = nsiConfDir, winDistPath = winDistPath, customizer = customizer, context = context)
prepareConfigurationFiles(nsiConfDir, winDistPath, customizer, context)
for (it in customizer.customNsiConfigurationFiles) {
val file = Path.of(it)
val copy = nsiConfDir.resolve(file.fileName)
@@ -68,65 +74,46 @@ internal suspend fun buildNsisInstaller(winDistPath: Path,
copy.setLastModifiedTime(FileTime.from(context.options.buildDateInSeconds, TimeUnit.SECONDS))
}
// Log final nsi directory to make debugging easier
val logDir = context.paths.buildOutputDir.resolve("log")
val nsiLogDir = logDir.resolve("nsi$suffix")
deleteDir(nsiLogDir)
copyDir(nsiConfDir, nsiLogDir)
val nsiLogDir = context.paths.buildOutputDir.resolve("log/nsi$suffix")
NioFiles.deleteRecursively(nsiLogDir)
NioFiles.copyRecursively(nsiConfDir, nsiLogDir)
val nsisZip = downloadFileToCacheLocation(
url = "https://packages.jetbrains.team/files/p/ij/intellij-build-dependencies/org/jetbrains/intellij/deps/nsis/" +
"NSIS-${context.dependenciesProperties.property("nsisBuild")}.zip",
communityRoot = context.paths.communityHomeDirRoot,
)
Decompressor.Zip(nsisZip).withZipExtensions().extract(box)
spanBuilder("run NSIS tool to build .exe installer for Windows").use {
val timeout = 2.hours
if (SystemInfoRt.isWindows) {
if (OsFamily.currentOs == OsFamily.WINDOWS) {
runProcess(
args = listOf(
"$box/NSIS/makensis.exe",
nsisBin.toString(),
"/V2",
"/DCOMMUNITY_DIR=$communityHome",
"/DCOMMUNITY_DIR=${communityHome}",
"/DIPR=${customizer.associateIpr}",
"/DOUT_DIR=${context.paths.artifactDir}",
"/DOUT_FILE=$outFileName",
"$box/nsiconf/idea.nsi",
"/DOUT_FILE=${outFileName}",
"${nsiConfDir}/idea.nsi",
),
workingDir = box,
timeout = timeout,
timeout
)
}
else {
val makeNsis = "$box/NSIS/Bin/makensis${if (JvmArchitecture.currentJvmArch == JvmArchitecture.x64) "" else "-${JvmArchitecture.currentJvmArch.fileSuffix}"}"
NioFiles.setExecutable(Path.of(makeNsis))
val args = if (SystemInfoRt.isMac) {
arrayOf(
"docker", "run", "--rm",
"--volume=$communityHome:$communityHome:ro",
"--volume=${context.paths.buildOutputDir}:${context.paths.buildOutputDir}",
"--workdir=$box",
"ubuntu:18.04"
)
}
else emptyArray()
runProcess(
args = listOf(
*args, makeNsis,
nsisBin.toString(),
"-V2",
"-DCOMMUNITY_DIR=$communityHome",
"-DCOMMUNITY_DIR=${communityHome}",
"-DIPR=${customizer.associateIpr}",
"-DOUT_DIR=${context.paths.artifactDir}",
"-DOUT_FILE=$outFileName",
"$box/nsiconf/idea.nsi",
"-DOUT_FILE=${outFileName}",
"${nsiConfDir}/idea.nsi",
),
workingDir = box,
timeout = timeout,
additionalEnvVariables = mapOf("NSISDIR" to "$box/NSIS"),
timeout,
additionalEnvVariables = mapOf("NSISDIR" to nsisDir.toString(), "LC_CTYPE" to "C.UTF-8"),
)
}
}
}
val installerFile = context.paths.artifactDir.resolve("$outFileName.exe")
check(Files.exists(installerFile)) {
"Windows installer wasn't created."
@@ -138,48 +125,59 @@ internal suspend fun buildNsisInstaller(winDistPath: Path,
return installerFile
}
private fun prepareConfigurationFiles(nsiConfDir: Path,
winDistPath: Path,
customizer: WindowsDistributionCustomizer,
context: BuildContext) {
private suspend fun prepareNsis(context: BuildContext, tempDir: Path): Pair<Path, Path> {
val nsisVersion = context.dependenciesProperties.property("nsisBuild")
val nsisUrl = "https://packages.jetbrains.team/files/p/ij/intellij-build-dependencies/org/jetbrains/intellij/deps/nsis/NSIS-${nsisVersion}.zip"
val nsisZip = downloadFileToCacheLocation(nsisUrl, context.paths.communityHomeDirRoot)
Decompressor.Zip(nsisZip).withZipExtensions().extract(tempDir)
val nsisDir = tempDir.resolve("NSIS")
require(nsisDir.isDirectory()) { "'${nsisDir.fileName}' is missing from ${nsisUrl}" }
val ext = if (OsFamily.currentOs == OsFamily.WINDOWS) ".exe" else "-${OsFamily.currentOs.dirName}-${JvmArchitecture.currentJvmArch.dirName}"
@Suppress("SpellCheckingInspection") val nsisBin = nsisDir.resolve("Bin/makensis${ext}")
require(nsisBin.isRegularFile()) { "'${nsisDir.fileName}' is missing" }
NioFiles.setExecutable(nsisBin)
return nsisDir to nsisBin
}
private fun prepareConfigurationFiles(nsiConfDir: Path, winDistPath: Path, customizer: WindowsDistributionCustomizer, context: BuildContext) {
val productProperties = context.productProperties
Files.writeString(nsiConfDir.resolve("paths.nsi"), $$"""
!define IMAGES_LOCATION "$${FileUtilRt.toSystemDependentName(customizer.installerImagesPath!!)}"
!define PRODUCT_PROPERTIES_FILE "$${FileUtilRt.toSystemDependentName("$winDistPath/bin/idea.properties")}"
!define PRODUCT_VM_OPTIONS_NAME $${productProperties.baseFileName}*.exe.vmoptions
!define PRODUCT_VM_OPTIONS_FILE "$${FileUtilRt.toSystemDependentName("${winDistPath}/bin/")}${PRODUCT_VM_OPTIONS_NAME}"
""".trimIndent())
Files.writeString(nsiConfDir.resolve("paths.nsi"), """
!define IMAGES_LOCATION "${FileUtilRt.toSystemDependentName(customizer.installerImagesPath!!)}"
!define PRODUCT_PROPERTIES_FILE "${FileUtilRt.toSystemDependentName("$winDistPath/bin/idea.properties")}"
!define PRODUCT_VM_OPTIONS_NAME ${productProperties.baseFileName}*.exe.vmoptions
!define PRODUCT_VM_OPTIONS_FILE "${FileUtilRt.toSystemDependentName("${winDistPath}/bin/")}${'$'}{PRODUCT_VM_OPTIONS_NAME}"
""")
val fileAssociations = if (customizer.fileAssociations.isEmpty()) "NoAssociation"
else customizer.fileAssociations.joinToString(separator = ",") { if (it.startsWith(".")) it else ".${it}" }
val fileAssociations =
if (customizer.fileAssociations.isEmpty()) "NoAssociation"
else customizer.fileAssociations.joinToString(separator = ",") { if (it.startsWith(".")) it else ".${it}" }
val appInfo = context.applicationInfo
Files.writeString(nsiConfDir.resolve("strings.nsi"), """
!define MANUFACTURER "${appInfo.shortCompanyName}"
!define MUI_PRODUCT "${customizer.getFullNameIncludingEdition(appInfo)}"
!define PRODUCT_FULL_NAME "${customizer.getFullNameIncludingEditionAndVendor(appInfo)}"
!define PRODUCT_EXE_FILE "${productProperties.baseFileName}64.exe"
!define PRODUCT_ICON_FILE "install.ico"
!define PRODUCT_UNINST_ICON_FILE "uninstall.ico"
!define PRODUCT_LOGO_FILE "logo.bmp"
!define PRODUCT_HEADER_FILE "headerlogo.bmp"
!define ASSOCIATION "$fileAssociations"
!define UNINSTALL_WEB_PAGE "${customizer.getUninstallFeedbackPageUrl(appInfo) ?: "feedback_web_page"}"
!define MANUFACTURER "${appInfo.shortCompanyName}"
!define MUI_PRODUCT "${customizer.getFullNameIncludingEdition(appInfo)}"
!define PRODUCT_FULL_NAME "${customizer.getFullNameIncludingEditionAndVendor(appInfo)}"
!define PRODUCT_EXE_FILE "${productProperties.baseFileName}64.exe"
!define PRODUCT_ICON_FILE "install.ico"
!define PRODUCT_UNINST_ICON_FILE "uninstall.ico"
!define PRODUCT_LOGO_FILE "logo.bmp"
!define PRODUCT_HEADER_FILE "headerlogo.bmp"
!define ASSOCIATION "$fileAssociations"
!define UNINSTALL_WEB_PAGE "${customizer.getUninstallFeedbackPageUrl(appInfo) ?: "feedback_web_page"}"
; if SHOULD_SET_DEFAULT_INSTDIR != 0 then default installation directory will be directory where highest-numbered IDE build has been installed
; set to 1 for release build
!define SHOULD_SET_DEFAULT_INSTDIR "0"
""".trimIndent())
; if SHOULD_SET_DEFAULT_INSTDIR != 0 then default installation directory will be directory where highest-numbered IDE build has been installed
; set to 1 for release build
!define SHOULD_SET_DEFAULT_INSTDIR "0"
""")
val versionString = if (appInfo.isEAP) "\${VER_BUILD}" else "\${MUI_VERSION_MAJOR}.\${MUI_VERSION_MINOR}"
val versionString = if (appInfo.isEAP) $$"${VER_BUILD}" else $$"${MUI_VERSION_MAJOR}.${MUI_VERSION_MINOR}"
val installDirAndShortcutName = customizer.getNameForInstallDirAndDesktopShortcut(appInfo, context.buildNumber)
Files.writeString(nsiConfDir.resolve("version.nsi"), """
!define MUI_VERSION_MAJOR "${appInfo.majorVersion}"
!define MUI_VERSION_MINOR "${appInfo.minorVersion}"
!define VER_BUILD ${context.buildNumber}
!define INSTALL_DIR_AND_SHORTCUT_NAME "$installDirAndShortcutName"
!define PRODUCT_WITH_VER "${"$"}{MUI_PRODUCT} $versionString"
!define PRODUCT_PATHS_SELECTOR "${context.systemSelector}"
""")
Files.writeString(nsiConfDir.resolve("version.nsi"), $$"""
!define MUI_VERSION_MAJOR "$${appInfo.majorVersion}"
!define MUI_VERSION_MINOR "$${appInfo.minorVersion}"
!define VER_BUILD $${context.buildNumber}
!define INSTALL_DIR_AND_SHORTCUT_NAME "$$installDirAndShortcutName"
!define PRODUCT_WITH_VER "${MUI_PRODUCT} $$versionString"
!define PRODUCT_PATHS_SELECTOR "$${context.systemSelector}"
""".trimIndent())
}