Skip to content

Commit

Permalink
add nsis installer
Browse files Browse the repository at this point in the history
  • Loading branch information
amir1376 committed Oct 10, 2024
1 parent a1c8f54 commit 2788b6c
Show file tree
Hide file tree
Showing 14 changed files with 602 additions and 32 deletions.
1 change: 1 addition & 0 deletions buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
20 changes: 10 additions & 10 deletions buildSrc/src/main/kotlin/buildlogic/CiUtils.kt
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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)) {
Expand All @@ -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)
Expand Down Expand Up @@ -89,7 +89,7 @@ object CiUtils {
fun movePackagedAndCreateSignature(
appVersion: Version,
packageName: String,
target: TargetFormat,
target: InstallerTargetFormat,
basePackagedAppsDir: File,
outputDir: File,
) {
Expand Down Expand Up @@ -148,4 +148,4 @@ object CiUtils {
*/
}

private fun TargetFormat.fileExtensionWithoutDot() = fileExt.substring(".".length)
private fun InstallerTargetFormat.fileExtensionWithoutDot() = fileExt.substring(".".length)
20 changes: 20 additions & 0 deletions compositeBuilds/plugins/installer-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
Original file line number Diff line number Diff line change
@@ -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")
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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"
}
}
Original file line number Diff line number Diff line change
@@ -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

Original file line number Diff line number Diff line change
@@ -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())

}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package ir.amirab.installer.utils

internal object Constants {
const val CREATE_INSTALLER_TASK_NAME = "createInstaller"
}
3 changes: 2 additions & 1 deletion compositeBuilds/plugins/settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ dependencyResolutionManagement{
}
}
}
include("git-version-plugin")
include("git-version-plugin")
include("installer-plugin")
Loading

0 comments on commit 2788b6c

Please sign in to comment.