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

add nsis installer #101

Merged
merged 1 commit into from
Oct 10, 2024
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
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