add nsis installer

This commit is contained in:
AmirHossein Abdolmotallebi 2024-10-02 09:04:46 +03:30
parent a1c8f54bc7
commit 2788b6cc76
14 changed files with 602 additions and 32 deletions

View File

@ -17,4 +17,5 @@ dependencies{
implementation(libs.semver)
implementation("ir.amirab.util:platform:1")
implementation("ir.amirab.plugin:git-version-plugin:1")
implementation("ir.amirab.plugin:installer-plugin:1")
}

View File

@ -1,21 +1,20 @@
package buildlogic
import io.github.z4kn4fein.semver.Version
import ir.amirab.installer.InstallerTargetFormat
import ir.amirab.util.platform.Platform
import org.jetbrains.compose.desktop.application.dsl.JvmApplicationDistributions
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
import java.io.File
object CiUtils {
fun getTargetFileName(
packageName: String,
appVersion: Version,
target: TargetFormat,
target: InstallerTargetFormat?,
): String {
val fileExtension = when (target) {
// we use archived for app image distribution
TargetFormat.AppImage -> {
// we use archived for app image distribution ( app image is a folder actually so there is no installer so we zip it instead)
null -> {
when (Platform.getCurrentPlatform()) {
Platform.Desktop.Linux -> "tar.gz"
Platform.Desktop.MacOS -> "tar.gz"
@ -28,7 +27,7 @@ object CiUtils {
}
val platformName = when (target) {
TargetFormat.AppImage -> Platform.getCurrentPlatform()
null -> Platform.getCurrentPlatform()
else -> {
val packageFileExt = target.fileExtensionWithoutDot()
requireNotNull(Platform.fromExecutableFileExtension(packageFileExt)) {
@ -41,9 +40,10 @@ object CiUtils {
fun getFileOfPackagedTarget(
baseOutputDir: File,
target: TargetFormat,
target: InstallerTargetFormat,
): File {
val folder = baseOutputDir.resolve(target.outputDirName)
val folder = baseOutputDir
// val folder = baseOutputDir.resolve(target.outputDirName)
val exeFile = kotlin.runCatching {
folder.walk().first {
it.name.endsWith(target.fileExt)
@ -89,7 +89,7 @@ object CiUtils {
fun movePackagedAndCreateSignature(
appVersion: Version,
packageName: String,
target: TargetFormat,
target: InstallerTargetFormat,
basePackagedAppsDir: File,
outputDir: File,
) {
@ -148,4 +148,4 @@ object CiUtils {
*/
}
private fun TargetFormat.fileExtensionWithoutDot() = fileExt.substring(".".length)
private fun InstallerTargetFormat.fileExtensionWithoutDot() = fileExt.substring(".".length)

View File

@ -0,0 +1,20 @@
plugins {
`kotlin-dsl`
}
repositories {
mavenCentral()
}
version = 1
group = "ir.amirab.plugin"
dependencies {
implementation("ir.amirab.util:platform:1")
implementation(libs.handlebarsJava)
}
gradlePlugin {
plugins {
create("installer-plugin") {
id = "ir.amirab.installer-plugin"
implementationClass = "ir.amirab.installer.InstallerPlugin"
}
}
}

View File

@ -0,0 +1,61 @@
package ir.amirab.installer
import ir.amirab.installer.extensiion.InstallerPluginExtension
import ir.amirab.installer.tasks.windows.NsisTask
import ir.amirab.installer.utils.Constants
import ir.amirab.util.platform.Platform
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.create
import org.gradle.kotlin.dsl.register
class InstallerPlugin : Plugin<Project> {
override fun apply(target: Project) {
val extension = target.extensions.create("installerPlugin", InstallerPluginExtension::class)
target.afterEvaluate {
registerTasks(target, extension)
}
}
private fun registerTasks(
project: Project,
extension: InstallerPluginExtension
) {
val windowConfig = extension.windowsConfig
val createInstallerTaskName = Constants.CREATE_INSTALLER_TASK_NAME
val createInstallerNsisTaskName = "${createInstallerTaskName}Nsis"
if (windowConfig != null) {
project.tasks
.register<NsisTask>(createInstallerNsisTaskName)
.configure {
dependsOn(extension.taskDependencies.toTypedArray())
this.nsisTemplate.set(requireNotNull(windowConfig.nsisTemplate) { "Nsis Template not provided" })
this.commonParams.set(windowConfig)
this.extraParams.set(windowConfig.extraParams)
this.destFolder.set(extension.outputFolder.get().asFile)
this.outputFileName.set(requireNotNull(windowConfig.outputFileName) { " outputFileName not provided " })
this.sourceFolder.set(requireNotNull(windowConfig.inputDir) { "inputDir not provided" })
}
}
project.tasks.register(createInstallerTaskName) {
// when we want to create installer we need to prepare its input first!
when (val platform = Platform.getCurrentPlatform()) {
Platform.Desktop.Linux -> {
// nothing yet
}
Platform.Desktop.MacOS -> {
// nothing yet
}
Platform.Desktop.Windows -> {
if (windowConfig != null) {
dependsOn(createInstallerNsisTaskName)
}
}
else -> error("unsupported platform: $platform")
}
}
}
}

View File

@ -0,0 +1,28 @@
package ir.amirab.installer
import ir.amirab.util.platform.Platform
enum class InstallerTargetFormat(
val id: String,
val targetOS: Platform,
) {
Deb("deb", Platform.Desktop.Linux),
Rpm("rpm", Platform.Desktop.Linux),
Dmg("dmg", Platform.Desktop.MacOS),
Pkg("pkg", Platform.Desktop.MacOS),
Exe("exe", Platform.Desktop.Windows),
Msi("msi", Platform.Desktop.Windows);
val isCompatibleWithCurrentOS: Boolean by lazy { isCompatibleWith(Platform.getCurrentPlatform()) }
fun isCompatibleWith(os: Platform): Boolean = os == targetOS
val outputDirName: String
get() = id
val fileExt: String
get() {
return ".$id"
}
}

View File

@ -0,0 +1,85 @@
package ir.amirab.installer.extensiion
import ir.amirab.installer.InstallerTargetFormat
import ir.amirab.installer.utils.Constants
import ir.amirab.util.platform.Platform
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.tasks.TaskProvider
import java.io.File
import java.io.Serializable
import javax.inject.Inject
abstract class InstallerPluginExtension {
@get:Inject
internal abstract val project: Project
abstract val outputFolder: DirectoryProperty
internal val taskDependencies = mutableListOf<Any>()
fun dependsOn(vararg tasks: Any) {
taskDependencies.addAll(tasks)
}
internal var windowsConfig: WindowsConfig? = null
private set
fun windows(
config: WindowsConfig.() -> Unit
) {
if (Platform.getCurrentPlatform() != Platform.Desktop.Windows) return
val windowsConfig = if (this.windowsConfig == null) {
WindowsConfig().also {
this.windowsConfig = it
}
} else {
this.windowsConfig!!
}
windowsConfig.config()
}
val createInstallerTask: TaskProvider<Task> by lazy {
project.tasks.named(Constants.CREATE_INSTALLER_TASK_NAME)
}
fun isThisPlatformSupported() = when (Platform.getCurrentPlatform()) {
Platform.Desktop.Windows -> windowsConfig != null
else -> {
false
}
}
fun getCreatedInstallerTargetFormats(): List<InstallerTargetFormat> {
return buildList<InstallerTargetFormat> {
when (Platform.getCurrentPlatform()) {
Platform.Desktop.Windows -> {
if (windowsConfig != null) {
add(InstallerTargetFormat.Exe)
}
}
else -> {}
}
}
}
}
data class WindowsConfig(
var appName: String? = null,
var appDisplayName: String? = null,
var appVersion: String? = null,
var appDisplayVersion: String? = null,
var iconFile: File? = null,
var licenceFile: File? = null,
var outputFileName: String? = null,
var inputDir: File? = null,
var nsisTemplate: File? = null,
var extraParams: Map<String, Any> = emptyMap()
) : Serializable

View File

@ -0,0 +1,91 @@
package ir.amirab.installer.tasks.windows
import com.github.jknack.handlebars.Context
import com.github.jknack.handlebars.Handlebars
import ir.amirab.installer.extensiion.WindowsConfig
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
import org.gradle.kotlin.dsl.mapProperty
import java.io.ByteArrayInputStream
import java.io.File
abstract class NsisTask : DefaultTask() {
@get:InputDirectory
abstract val sourceFolder: DirectoryProperty
@get:OutputDirectory
abstract val destFolder: DirectoryProperty
@get:Input
abstract val outputFileName: Property<String>
@get:InputFile
abstract val nsisTemplate: Property<File>
@get:Input
abstract val commonParams: Property<WindowsConfig>
@get:Input
val extraParams: MapProperty<String, Any> = project.objects.mapProperty<String, Any>()
@get:Internal
abstract val nsisExecutable: Property<File>
init {
nsisExecutable.convention(
project.provider { File("C:\\Program Files (x86)\\NSIS\\makensis.exe") }
)
}
private fun createHandleBarContext(): Context {
val commonParams = commonParams.get()
val common = mapOf(
"app_name" to commonParams.appName!!,
"app_display_name" to commonParams.appDisplayName!!,
"app_version" to commonParams.appVersion!!,
"app_display_version" to commonParams.appDisplayVersion!!,
"license_file" to commonParams.licenceFile!!,
"icon_file" to commonParams.iconFile!!,
)
val overrides = mapOf(
"input_dir" to sourceFolder.get().asFile.absolutePath,
"output_file" to "${destFolder.file(outputFileName).get().asFile.path}.exe",
)
return Context.newContext(
extraParams
.get()
.plus(common)
.plus(overrides)
)
}
@TaskAction
fun run() {
val executable = nsisExecutable.get()
val scriptTemplate = nsisTemplate.get()
val handlebars = Handlebars()
val context = createHandleBarContext()
val script = handlebars.compileInline(
scriptTemplate.readText()
).apply(context)
logger.debug("NSIS Script:")
logger.debug(script)
project.exec {
executable(
executable,
)
args("-")
standardInput = ByteArrayInputStream(script.toByteArray())
}
}
}

View File

@ -0,0 +1,5 @@
package ir.amirab.installer.utils
internal object Constants {
const val CREATE_INSTALLER_TASK_NAME = "createInstaller"
}

View File

@ -6,3 +6,4 @@ dependencyResolutionManagement{
}
}
include("git-version-plugin")
include("installer-plugin")

View File

@ -1,8 +1,10 @@
import buildlogic.*
import buildlogic.versioning.*
import ir.amirab.installer.InstallerTargetFormat
import org.jetbrains.changelog.Changelog
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
import ir.amirab.util.platform.Platform
import org.jetbrains.compose.desktop.application.dsl.TargetFormat.*
plugins {
id(MyPlugins.kotlin)
@ -12,9 +14,9 @@ plugins {
id(Plugins.changeLog)
id(Plugins.ksp)
id(Plugins.aboutLibraries)
id("ir.amirab.installer-plugin")
// id(MyPlugins.proguardDesktop)
}
dependencies {
implementation(libs.decompose)
implementation(libs.decompose.jbCompose)
@ -102,7 +104,7 @@ compose {
mainClass = "$desktopPackageName.AppKt"
nativeDistributions {
modules("java.instrument", "jdk.unsupported")
targetFormats(TargetFormat.Msi, TargetFormat.Deb)
targetFormats(Msi, Deb)
if (Platform.getCurrentPlatform() == Platform.Desktop.Linux) {
// filekit library requires this module in linux.
modules("jdk.security.auth")
@ -114,21 +116,21 @@ compose {
val menuGroupName = getPrettifiedAppName()
licenseFile.set(rootProject.file("LICENSE"))
linux {
debPackageVersion = getAppVersionStringForPackaging(TargetFormat.Deb)
rpmPackageVersion = getAppVersionStringForPackaging(TargetFormat.Rpm)
debPackageVersion = getAppVersionStringForPackaging(Deb)
rpmPackageVersion = getAppVersionStringForPackaging(Rpm)
appCategory = "Network"
iconFile = project.file("icons/icon.png")
menuGroup = menuGroupName
shortcut = true
}
macOS {
pkgPackageVersion = getAppVersionStringForPackaging(TargetFormat.Pkg)
dmgPackageVersion = getAppVersionStringForPackaging(TargetFormat.Dmg)
pkgPackageVersion = getAppVersionStringForPackaging(Pkg)
dmgPackageVersion = getAppVersionStringForPackaging(Dmg)
iconFile = project.file("icons/icon.icns")
}
windows {
exePackageVersion = getAppVersionStringForPackaging(TargetFormat.Exe)
msiPackageVersion = getAppVersionStringForPackaging(TargetFormat.Msi)
exePackageVersion = getAppVersionStringForPackaging(Exe)
msiPackageVersion = getAppVersionStringForPackaging(Msi)
upgradeUuid = properties["INSTALLER.WINDOWS.UPGRADE_UUID"]?.toString()
iconFile = project.file("icons/icon.ico")
console = false
@ -142,6 +144,32 @@ compose {
}
}
installerPlugin {
dependsOn("createReleaseDistributable")
outputFolder.set(layout.buildDirectory.dir("custom-installer"))
windows {
appName = getAppName()
appDisplayName = getPrettifiedAppName()
appVersion = getAppVersionStringForPackaging(Exe)
appDisplayVersion = getAppVersionString()
inputDir = project.file("build/compose/binaries/main-release/app/${getAppName()}")
outputFileName = getAppName()
licenceFile = rootProject.file("LICENSE")
iconFile = project.file("icons/icon.ico")
nsisTemplate = project.file("resources/installer/nsis-script-template.nsi")
extraParams = mapOf(
"app_publisher" to "abdownloadmanager.com",
"app_version_with_build" to "${getAppVersionStringForPackaging(Exe)}.0",
"source_code_url" to "https://github.com/amir1376/ab-download-manager",
"project_website" to "www.abdownloadmanager.com",
"copyright" to "© 2024-present AB Download Manager App",
"header_image_file" to project.file("resources/installer/abdm-header-image.bmp"),
"sidebar_image_file" to project.file("resources/installer/abdm-sidebar-image.bmp")
)
}
}
// generate a file with these constants
buildConfig {
@ -240,27 +268,51 @@ val createDistributableAppArchive by tasks.registering {
val createBinariesForCi by tasks.registering {
val nativeDistributions = compose.desktop.application.nativeDistributions
val mainRelease = nativeDistributions.outputBaseDir.dir("main-release")
dependsOn("packageReleaseDistributionForCurrentOS")
if (installerPlugin.isThisPlatformSupported()) {
dependsOn(installerPlugin.createInstallerTask)
inputs.dir(installerPlugin.outputFolder)
} else {
dependsOn("packageReleaseDistributionForCurrentOS")
inputs.dir(mainRelease)
}
dependsOn(createDistributableAppArchive)
inputs.property("appVersion", getAppVersionString())
inputs.dir(mainRelease)
inputs.dir(distributableAppArchiveDir)
outputs.dir(ciDir.binariesDir)
doLast {
val output = ciDir.binariesDir.get().asFile
val packageName = appPackageNameByComposePlugin
output.deleteRecursively()
val allowedTarget = nativeDistributions.targetFormats.filter { it.isCompatibleWithCurrentOS }
for (target in allowedTarget) {
CiUtils.movePackagedAndCreateSignature(
getAppVersion(),
packageName,
target,
mainRelease.get().asFile,
output,
)
if (installerPlugin.isThisPlatformSupported()) {
val targets = installerPlugin.getCreatedInstallerTargetFormats()
for (target in targets) {
CiUtils.movePackagedAndCreateSignature(
getAppVersion(),
packageName,
target,
installerPlugin.outputFolder.get().asFile,
output,
)
}
logger.lifecycle("app packages for '${targets.joinToString(", ") { it.name }}' written in $output using the installer plugin")
} else {
val allowedTargets = nativeDistributions
.targetFormats.filter { it.isCompatibleWithCurrentOS }
.map {
it.toInstallerTargetFormat()
}
for (target in allowedTargets) {
CiUtils.movePackagedAndCreateSignature(
getAppVersion(),
packageName,
target,
mainRelease.get().asFile.resolve(target.outputDirName),
output,
)
}
logger.lifecycle("app packages for '${allowedTargets.joinToString(", ") { it.name }}' written in $output using compose packager tool")
}
logger.lifecycle("app packages for '${allowedTarget.joinToString(", ") { it.name }}' written in $output")
val appArchiveDistributableDir = distributableAppArchiveDir.get().asFile
CiUtils.copyAndHashToDestination(
distributableAppArchiveDir.get().asFile.resolve(
@ -272,7 +324,7 @@ val createBinariesForCi by tasks.registering {
CiUtils.getTargetFileName(
packageName,
getAppVersion(),
TargetFormat.AppImage,
null, // this is not an installer (it will be automatically converted to current os name
)
)
logger.lifecycle("distributable app archive written in ${output}")
@ -299,3 +351,15 @@ val createReleaseFolderForCi by tasks.registering {
dependsOn(createBinariesForCi, createChangeNoteForCi)
}
// ======= end of GitHub action stuff
fun TargetFormat.toInstallerTargetFormat(): InstallerTargetFormat {
return when (this) {
AppImage -> error("$this is not recognized as installer")
Deb -> InstallerTargetFormat.Deb
Rpm -> InstallerTargetFormat.Rpm
Dmg -> InstallerTargetFormat.Dmg
Pkg -> InstallerTargetFormat.Pkg
Exe -> InstallerTargetFormat.Exe
Msi -> InstallerTargetFormat.Msi
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

View File

@ -0,0 +1,212 @@
Unicode True
RequestExecutionLevel user
SetCompressor /SOLID lzma
!include "LogicLib.nsh"
!include "MUI2.nsh"
!define APP_PUBLISHER "{{ app_publisher }}"
!define APP_NAME "{{ app_name }}"
!define APP_DISPLAY_NAME "{{ app_display_name }}"
!define APP_VERSION "{{ app_version }}"
!define APP_VERSION_WITH_BUILD "{{ app_version_with_build }}"
!define APP_DISPLAY_VERSION "{{ app_display_version }}"
!define SOURCE_CODE_URL "{{ source_code_url }}"
!define PROJECT_WEBSITE "{{ project_website }}"
!define COPYRIGHT "{{ copyright }}"
!define INPUT_DIR "{{ input_dir }}"
!define LICENSE_FILE "{{ license_file }}"
!define MAIN_BINARY_NAME "${APP_NAME}"
!define SIDEBAR_IMAGE "{{ sidebar_image_file }}"
!define HEADER_IMAGE "{{ header_image_file }}"
!define ICON_FILE "{{ icon_file }}"
!define REG_UNINSTALL_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}"
!define REG_RUN_KEY "Software\Microsoft\Windows\CurrentVersion\Run\${APP_NAME}"
!define REG_APP_KEY "Software\${APP_NAME}"
; icon for this installer!
Icon "${ICON_FILE}"
!define MUI_ICON "${ICON_FILE}"
!define MUI_UNICON "${ICON_FILE}"
!if "${SIDEBAR_IMAGE}" != ""
!define MUI_WELCOMEFINISHPAGE_BITMAP "${SIDEBAR_IMAGE}"
!define MUI_UNWELCOMEFINISHPAGE_BITMAP "${SIDEBAR_IMAGE}"
!endif
!if "${HEADER_IMAGE}" != ""
!define MUI_HEADERIMAGE
!define MUI_HEADERIMAGE_BITMAP "${HEADER_IMAGE}"
!define MUI_UNHEADERIMAGE
!define MUI_UNHEADERIMAGE_BITMAP "${HEADER_IMAGE}"
!endif
VIProductVersion "${APP_VERSION_WITH_BUILD}"
VIAddVersionKey "ProductName" "${APP_DISPLAY_NAME}"
VIAddVersionKey "FileDescription" "${APP_DISPLAY_NAME}"
VIAddVersionKey "LegalCopyright" "${COPYRIGHT}"
VIAddVersionKey "FileVersion" "${APP_VERSION_WITH_BUILD}"
VIAddVersionKey "ProductVersion" "${APP_VERSION_WITH_BUILD}"
Name "${APP_DISPLAY_NAME}"
OutFile "{{ output_file }}"
InstallDir "$LOCALAPPDATA\${APP_NAME}"
!define INSTALL_DIR `$INSTDIR`
Function .onInit
; Call RestorePreviousInstallLocation
FunctionEnd
; configure instfiles page
!define MUI_FINISHPAGE_NOAUTOCLOSE
!define MUI_INSTFILESPAGE_NOAUTOCLOSE
; configure finish page
!define MUI_FINISHPAGE_LINK "Open project in GitHub"
!define MUI_FINISHPAGE_LINK_LOCATION "${SOURCE_CODE_URL}"
!define MUI_FINISHPAGE_RUN
!define MUI_FINISHPAGE_RUN_FUNCTION RunMainBinary
;Installation Pages
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_LICENSE "${LICENSE_FILE}"
!insertmacro MUI_PAGE_COMPONENTS
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_PAGE_FINISH
;Uninstallation Pages
!insertmacro MUI_UNPAGE_WELCOME
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_UNPAGE_FINISH
; set language
!insertmacro MUI_LANGUAGE "English"
; a macro clear files to cleanup installation folder
!macro clearFiles
RmDir /r "${INSTALL_DIR}\app"
RmDir /r "${INSTALL_DIR}\runtime"
Delete "${INSTALL_DIR}\${MAIN_BINARY_NAME}.exe"
Delete "${INSTALL_DIR}\${MAIN_BINARY_NAME}.ico"
Delete "${INSTALL_DIR}\uninstall.exe"
RmDir "${INSTALL_DIR}"
!macroend
Function RunMainBinary
Exec "${INSTALL_DIR}\${MAIN_BINARY_NAME}.exe"
FunctionEnd
!macro GetBestExecutableName result
StrCpy ${result} "${MAIN_BINARY_NAME}.exe"
!macroend
; Function RestorePreviousInstallLocation
; ReadRegStr $4 SHCTX "${REG_APP_KEY}" "InstallPath"
; ${if} $4 != ""
; StrCpy $INSTDIR $4
; ${endif}
; FunctionEnd
; I should improve this.
!macro closeApp
!insertmacro GetBestExecutableName $1
DetailPrint "Stopping Executable $1"
; I don't wanna kill myself!
${If} "$EXEFILE" != "$1"
ExecWait 'taskkill /F /IM "$1"' $0
${Else}
DetailPrint "It seems that installer file name is same as app executable name"
DetailPrint "Please close app manually"
; don't sleep the script for nothing.
StrCpy $0 "1"
${EndIf}
${If} $0 == "0"
Sleep 500
BringToFront ; when we sleep it seems that window goes down
DetailPrint "Current app stopped successfully"
${Endif}
!macroend
!macro CreateStartMenu
createDirectory "$SMPROGRAMS\${APP_DISPLAY_NAME}"
createShortCut "$SMPROGRAMS\${APP_DISPLAY_NAME}\${APP_DISPLAY_NAME}.lnk" "${INSTALL_DIR}\${MAIN_BINARY_NAME}.exe" "" "${INSTALL_DIR}\${MAIN_BINARY_NAME}.ico"
!macroend
!macro RemoveStartMenu
RmDir /r "$SMPROGRAMS\${APP_DISPLAY_NAME}"
!macroend
!macro CreateDesktopShortcut
CreateShortcut "$DESKTOP\${APP_DISPLAY_NAME}.lnk" "${INSTALL_DIR}\${MAIN_BINARY_NAME}.exe" "" "${INSTALL_DIR}\${MAIN_BINARY_NAME}.ico"
!macroend
!macro RemoveDesktopShortCut
Delete "$DESKTOP\${APP_DISPLAY_NAME}.lnk"
!macroend
Section "${APP_DISPLAY_NAME}"
SectionInstType RO
DetailPrint "Closing app (if any)"
!insertmacro closeApp
DetailPrint "clearing old app (if any)"
!insertmacro clearFiles
DetailPrint "writing new data"
SetOutPath "${INSTALL_DIR}"
CreateDirectory "${INSTALL_DIR}"
WriteUninstaller "${INSTALL_DIR}\uninstall.exe"
File /nonfatal /r "${INPUT_DIR}\"
; Registry information for add/remove programs
WriteRegStr SHCTX "${REG_UNINSTALL_KEY}" "DisplayName" "${APP_DISPLAY_NAME}"
WriteRegStr SHCTX "${REG_UNINSTALL_KEY}" "DisplayIcon" "$\"${INSTALL_DIR}\${MAIN_BINARY_NAME}.exe$\""
WriteRegStr SHCTX "${REG_UNINSTALL_KEY}" "DisplayVersion" "${APP_VERSION}"
WriteRegStr SHCTX "${REG_UNINSTALL_KEY}" "Publisher" "${APP_PUBLISHER}"
WriteRegStr SHCTX "${REG_UNINSTALL_KEY}" "InstallLocation" "$\"${INSTALL_DIR}$\""
WriteRegStr SHCTX "${REG_UNINSTALL_KEY}" "UninstallString" "$\"${INSTALL_DIR}\uninstall.exe$\""
WriteRegDWORD SHCTX "${REG_UNINSTALL_KEY}" "NoModify" "1"
WriteRegDWORD SHCTX "${REG_UNINSTALL_KEY}" "NoRepair" "1"
; Registry keys for app installation path and version
WriteRegStr SHCTX "${REG_APP_KEY}" "InstallPath" "${INSTALL_DIR}"
WriteRegStr SHCTX "${REG_APP_KEY}" "Version" "${APP_VERSION}"
SectionEnd
Section "Start Menu"
!insertmacro CreateStartMenu
SectionEnd
Section "Desktop Shortcut"
!insertmacro CreateDesktopShortcut
SectionEnd
Section "Uninstall"
!insertmacro closeApp
!insertmacro clearFiles
!insertmacro RemoveStartMenu
!insertmacro RemoveDesktopShortCut
DeleteRegKey SHCTX "${REG_UNINSTALL_KEY}"
DeleteRegKey SHCTX "${REG_APP_KEY}"
; remove auto start on boot registry
DeleteRegValue SHCTX "${REG_RUN_KEY}" "${APP_NAME}"
SectionEnd

View File

@ -117,6 +117,8 @@ arrow-opticKsp = { module = "io.arrow-kt:arrow-optics-ksp-plugin", version.ref =
androidx-datastore = { module = "androidx.datastore:datastore", version.ref = "datastore" }
osThemeDetector = { module = "com.github.Dansoftowner:jSystemThemeDetector", version.ref = "osThemeDetector" }
handlebarsJava = "com.github.jknack:handlebars:4.4.0"
[plugins]
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
compose = { id = "org.jetbrains.compose", version.ref = "compose" }