diff --git a/cmd/plt-build/build.gradle.kts b/cmd/plt-build/build.gradle.kts index 767fa68..3302c26 100644 --- a/cmd/plt-build/build.gradle.kts +++ b/cmd/plt-build/build.gradle.kts @@ -10,6 +10,10 @@ repositories { mavenCentral() } + +val kotlinVersion = "2.2.10" +val kotlinCoroutinesVersion = "1.7.0-RC" + dependencies { testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") @@ -18,6 +22,11 @@ dependencies { testImplementation(kotlin("test")) implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0") implementation(kotlin("reflect")) + implementation("org.jetbrains.kotlin:kotlin-scripting-common:${kotlinVersion}") + implementation("org.jetbrains.kotlin:kotlin-scripting-jvm:${kotlinVersion}") + implementation("org.jetbrains.kotlin:kotlin-scripting-dependencies:${kotlinVersion}") + implementation("org.jetbrains.kotlin:kotlin-scripting-dependencies-maven:${kotlinVersion}") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${kotlinCoroutinesVersion}") } kotlin { @@ -27,4 +36,4 @@ kotlin { tasks.test { useJUnitPlatform() } -project.layout.buildDirectory.set(file("../build")) \ No newline at end of file +project.layout.buildDirectory.set(file("../../build")) \ No newline at end of file diff --git a/cmd/plt-build/src/main/kotlin/moe/rosa/planterette/api/Capability.kt b/cmd/plt-build/src/main/kotlin/moe/rosa/planterette/api/Capability.kt new file mode 100644 index 0000000..fdfb969 --- /dev/null +++ b/cmd/plt-build/src/main/kotlin/moe/rosa/planterette/api/Capability.kt @@ -0,0 +1,16 @@ +package moe.rosa.planterette.api + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +sealed interface Capability { + val id: String + + @SerialName("basic") + data class Basic(override val id: String) : Capability + + @SerialName("dbus") + data class DBus(override val id: String, val own: List) : Capability +} + diff --git a/cmd/plt-build/src/main/kotlin/moe/rosa/planterette/api/Manifest.kt b/cmd/plt-build/src/main/kotlin/moe/rosa/planterette/api/Manifest.kt new file mode 100644 index 0000000..13a1131 --- /dev/null +++ b/cmd/plt-build/src/main/kotlin/moe/rosa/planterette/api/Manifest.kt @@ -0,0 +1,36 @@ +package moe.rosa.planterette.api + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class Manifest( + val metadata: Metadata, + val executable: Executable, + val capabilities: List, + val permissions: List +) { + @Serializable + data class Metadata( + var id: String, + var name: String, + var description: String, + var author: String, + var icon: String, + var version: Int, + @SerialName("version_name") var versionName: String + ) { + constructor() : this("", "", "", "", "", -1, "") + } + + @Serializable + data class Executable( + @SerialName("base_image") var baseImage: String, + var binary: String, + val args: MutableList, + val env: MutableMap + ) { + constructor() : this("", "", mutableListOf(), mutableMapOf()) + } +} + diff --git a/cmd/plt-build/src/main/kotlin/moe/rosa/planterette/dsl/DSL.kt b/cmd/plt-build/src/main/kotlin/moe/rosa/planterette/dsl/DSL.kt new file mode 100644 index 0000000..91185f5 --- /dev/null +++ b/cmd/plt-build/src/main/kotlin/moe/rosa/planterette/dsl/DSL.kt @@ -0,0 +1,211 @@ +@file:Suppress("unused") +package moe.rosa.planterette.dsl + +import moe.rosa.planterette.api.Capability +import moe.rosa.planterette.api.Manifest + + +@Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +@DslMarker +annotation class PlanteretteBlockMarker + +@Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +@PlanteretteBlockMarker +annotation class MetadataBlockMarker + +@Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +@PlanteretteBlockMarker +annotation class ExecutableBlockMarker + +@Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +@PlanteretteBlockMarker +annotation class CapabilitiesBlockMarker + +@Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +@CapabilitiesBlockMarker +annotation class DBusBlockMarker + +@Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +@PlanteretteBlockMarker +annotation class PermissionsBlockMarker + +@Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +@PermissionsBlockMarker +annotation class FilesystemBlockMarker + +@PlanteretteBlockMarker +data class PlanteretteBlock( + var metadata: MetadataBlock, + var executable: ExecutableBlock, + var capabilities: CapabilitiesBlock, + var permissions: PermissionsBlock +) + +@MetadataBlockMarker +data class MetadataBlock(var metadata: Manifest.Metadata) + +@ExecutableBlockMarker +data class ExecutableBlock(var executable: Manifest.Executable) + +@CapabilitiesBlockMarker +data class CapabilitiesBlock(var capabilities: MutableList) + +// TODO add the rest of this +@DBusBlockMarker +data class DBusBlock(var own: MutableList) + +@PermissionsBlockMarker +data class PermissionsBlock(var permissions: MutableList) + +@FilesystemBlockMarker +class FilesystemBlock(var filesystemPermissions: MutableList) + +@PlanteretteBlockMarker +fun planterette(init: PlanteretteBlock.() -> Unit) { + PlanteretteBlock(MetadataBlock(Manifest.Metadata()), ExecutableBlock(Manifest.Executable()), CapabilitiesBlock(mutableListOf()), PermissionsBlock(mutableListOf())).apply(init) +} + +@PlanteretteBlockMarker +fun PlanteretteBlock.metadata(init: @MetadataBlockMarker MetadataBlock.() -> Unit) { + this.metadata.apply(init) +} + +@MetadataBlockMarker +fun MetadataBlock.id(id: String) { + this.metadata.id = id +} + +@MetadataBlockMarker +fun MetadataBlock.name(name: String) { + this.metadata.name = name +} + +@MetadataBlockMarker +fun MetadataBlock.description(description: String) { + this.metadata.description = description +} + +@MetadataBlockMarker +fun MetadataBlock.author(author: String) { + this.metadata.author = author +} + +@MetadataBlockMarker +fun MetadataBlock.icon(icon: String) { + this.metadata.icon = icon +} + +@MetadataBlockMarker +fun MetadataBlock.version(number: Int, name: String) { + this.metadata.version = number + this.metadata.versionName = name +} + +@PlanteretteBlockMarker +fun PlanteretteBlock.executable(init: @ExecutableBlockMarker ExecutableBlock.() -> Unit) { + this.executable.apply(init) +} + +@ExecutableBlockMarker +fun ExecutableBlock.baseImage(baseImage: String) { + this.executable.baseImage = baseImage +} + +@ExecutableBlockMarker +fun ExecutableBlock.binary(binary: String) { + this.executable.binary = binary +} + +@ExecutableBlockMarker +fun ExecutableBlock.args(vararg args: String) { + this.executable.args.addAll(args) +} + +@ExecutableBlockMarker +fun ExecutableBlock.env(vararg env: Pair) { + this.executable.env.putAll(env) +} + +@PlanteretteBlockMarker +fun PlanteretteBlock.capabilities(init: @CapabilitiesBlockMarker CapabilitiesBlock.() -> Unit) { + this.capabilities.apply(init) +} + +internal fun CapabilitiesBlock.addBasic(id: String) { + this.capabilities.add(Capability.Basic(id)) +} + +@CapabilitiesBlockMarker +fun CapabilitiesBlock.portal() { + addBasic("moe.rosa.capabilities.Portal") +} + +@CapabilitiesBlockMarker +fun CapabilitiesBlock.secrets() { + addBasic("moe.rosa.capabilities.Secrets") +} + +@CapabilitiesBlockMarker +fun CapabilitiesBlock.developer() { + addBasic("moe.rosa.capabilities.Developer") +} + +@CapabilitiesBlockMarker +fun CapabilitiesBlock.graphics() { + addBasic("moe.rosa.capabilities.Graphics") +} + +@CapabilitiesBlockMarker +fun CapabilitiesBlock.audio() { + addBasic("moe.rosa.capabilities.Audio") +} + +@CapabilitiesBlockMarker +fun CapabilitiesBlock.dbus(init: @DBusBlockMarker DBusBlock.() -> Unit) { + val dbus = DBusBlock(mutableListOf()) + this.capabilities.add(Capability.DBus("moe.rosa.capabilities.DBus", dbus.own)) +} + +@PlanteretteBlockMarker +fun PlanteretteBlock.permissions(init: @PermissionsBlockMarker PermissionsBlock.() -> Unit) { + this.permissions.apply(init) +} + +internal fun PermissionsBlock.addBasic(id: String) { + this.permissions.add(Capability.Basic(id)) +} + +@PermissionsBlockMarker +fun PermissionsBlock.notifications() { + addBasic("moe.rosa.permissions.Notifications") +} + +@PermissionsBlockMarker +fun PermissionsBlock.filesystem(init: @FilesystemBlockMarker FilesystemBlock.() -> Unit) { + this.permissions.addAll(FilesystemBlock(mutableListOf()).apply(init).filesystemPermissions) +} + +@FilesystemBlockMarker +fun FilesystemBlock.fileManager() { + this.filesystemPermissions.add(Capability.Basic("moe.rosa.permissions.filesystem.FileManager")) +} + +@FilesystemBlockMarker +fun FilesystemBlock.etc() { + this.filesystemPermissions.add(Capability.Basic("moe.rosa.permissions.filesystem.Etc")) +} + +@FilesystemBlockMarker +fun FilesystemBlock.tmp() { + this.filesystemPermissions.add(Capability.Basic("moe.rosa.permissions.filesystem.Tmp")) +} + +@PermissionsBlockMarker +fun PermissionsBlock.network() { + addBasic("moe.rosa.permissions.Network") +} + +@PermissionsBlockMarker +fun PermissionsBlock.system() { + addBasic("moe.rosa.permissions.System") +} \ No newline at end of file diff --git a/cmd/plt-build/src/main/kotlin/moe/rosa/planterette/dsl/PltRecipe.kt b/cmd/plt-build/src/main/kotlin/moe/rosa/planterette/dsl/PltRecipe.kt new file mode 100644 index 0000000..a39fe4c --- /dev/null +++ b/cmd/plt-build/src/main/kotlin/moe/rosa/planterette/dsl/PltRecipe.kt @@ -0,0 +1,48 @@ +package moe.rosa.planterette.dsl + +import kotlinx.coroutines.runBlocking +import moe.rosa.planterette.api.Capability +import moe.rosa.planterette.api.Manifest +import kotlin.script.experimental.annotations.KotlinScript +import kotlin.script.experimental.api.* +import kotlin.script.experimental.dependencies.* +import kotlin.script.experimental.dependencies.maven.MavenDependenciesResolver +import kotlin.script.experimental.jvm.JvmDependency +import kotlin.script.experimental.jvm.dependenciesFromCurrentContext +import kotlin.script.experimental.jvm.jvm + +@KotlinScript( + fileExtension = "plt.kts", + compilationConfiguration = ScriptWithMavenDepsConfiguration::class +) +open class PltRecipe + +object ScriptWithMavenDepsConfiguration : ScriptCompilationConfiguration( + { + defaultImports(DependsOn::class, Repository::class) + defaultImports.append("moe.rosa.planterette.dsl.*") + jvm { + dependenciesFromCurrentContext( + "script", + "kotlin-scripting-dependencies" + ) + } + refineConfiguration { + onAnnotations(DependsOn::class, Repository::class, handler = ::configureMavenDepsOnAnnotations) + } + } +) + +private val resolver = CompoundDependenciesResolver(FileSystemDependenciesResolver(), MavenDependenciesResolver()) + +fun configureMavenDepsOnAnnotations(context: ScriptConfigurationRefinementContext): ResultWithDiagnostics { + val annotations = context.collectedData?.get(ScriptCollectedData.collectedAnnotations)?.takeIf { it.isNotEmpty() } + ?: return context.compilationConfiguration.asSuccess() + return runBlocking { + resolver.resolveFromScriptSourceAnnotations(annotations) + }.onSuccess { + context.compilationConfiguration.with { + dependencies.append(JvmDependency(it)) + }.asSuccess() + } +} \ No newline at end of file