migrate docker-compose convention plugin out of buildSrc

This commit is contained in:
Adam 2022-10-02 12:11:06 +02:00
parent 105555bf6c
commit f804c39a3b
32 changed files with 564 additions and 239 deletions

View file

@ -1,8 +1,9 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
`kotlin-dsl`
kotlin("jvm") version embeddedKotlinVersion
id("kafkatorio.conventions.kotlin-dsl")
// `kotlin-dsl`
// kotlin("jvm") version embeddedKotlinVersion
}
dependencies {
@ -34,20 +35,20 @@ dependencies {
implementation(libs.kotlinx.cli)
}
val gradleJvmTarget = "11"
kotlin {
jvmToolchain {
(this as JavaToolchainSpec).languageVersion.set(JavaLanguageVersion.of(gradleJvmTarget))
}
}
kotlinDslPluginOptions {
jvmTarget.set(gradleJvmTarget)
}
tasks.withType<KotlinCompile>().configureEach {
kotlinOptions {
jvmTarget = gradleJvmTarget
}
}
//val gradleJvmTarget = "11"
//
//kotlin {
// jvmToolchain {
// (this as JavaToolchainSpec).languageVersion.set(JavaLanguageVersion.of(gradleJvmTarget))
// }
//}
//
//kotlinDslPluginOptions {
// jvmTarget.set(gradleJvmTarget)
//}
//
//tasks.withType<KotlinCompile>().configureEach {
// kotlinOptions {
// jvmTarget = gradleJvmTarget
// }
//}

View file

@ -0,0 +1,24 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm")
}
val gradleJvmTarget = "11"
dependencies {
implementation(platform(kotlin("bom")))
implementation(gradleApi())
}
kotlin {
jvmToolchain {
(this as JavaToolchainSpec).languageVersion.set(JavaLanguageVersion.of(gradleJvmTarget))
}
}
tasks.withType<KotlinCompile>().configureEach {
kotlinOptions {
jvmTarget = gradleJvmTarget
}
}

View file

@ -0,0 +1,17 @@
package kafkatorio.common
import org.gradle.api.artifacts.Configuration
fun Configuration.asProvider() {
isVisible = false
isCanBeResolved = false
isCanBeConsumed = true
}
fun Configuration.asConsumer() {
isVisible = false
isCanBeResolved = true
isCanBeConsumed = false
}

View file

@ -16,3 +16,16 @@ dependencyResolutionManagement {
}
}
}
include(
":modules:common-lib",
":modules:build-conventions",
":modules:docker-compose",
":modules:factorio-mod",
":modules:typescript",
)
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")

View file

@ -1,63 +1,63 @@
package kafkatorio.conventions.infra
import kafkatorio.tasks.DockerEnvUpdateTask
import kafkatorio.extensions.DOCKER_COMPOSE_TASK_GROUP
import org.jetbrains.kotlin.util.parseSpaceSeparatedArgs
plugins {
base
id("kafkatorio.conventions.infra.process-runner")
}
val dockerComposeProjectName: String by extra(rootProject.name)
val dockerSrcDir: Directory by extra(layout.projectDirectory.dir("src"))
val dockerUp by tasks.registering(Exec::class) {
group = DOCKER_COMPOSE_TASK_GROUP
dependsOn(tasks.assemble)
logging.captureStandardOutput(LogLevel.LIFECYCLE)
workingDir = dockerSrcDir.asFile
commandLine = parseSpaceSeparatedArgs(""" docker-compose up -d """)
}
val dockerDown by tasks.registering(Exec::class) {
group = DOCKER_COMPOSE_TASK_GROUP
logging.captureStandardOutput(LogLevel.LIFECYCLE)
workingDir = dockerSrcDir.asFile
commandLine = parseSpaceSeparatedArgs(""" docker-compose down """)
}
val dockerRemove by tasks.registering(Exec::class) {
group = DOCKER_COMPOSE_TASK_GROUP
logging.captureStandardOutput(LogLevel.LIFECYCLE)
workingDir = dockerSrcDir.asFile
commandLine = parseSpaceSeparatedArgs(""" docker-compose rm --stop -v -f """)
}
tasks.matching { it.name == "processRun" }.configureEach { dependsOn(dockerUp, dockerEnvUpdate) }
tasks.matching { it.name == "processKill" }.configureEach { dependsOn(dockerDown) }
val dockerEnvUpdate by tasks.registering(DockerEnvUpdateTask::class) {
dotEnvFile.set(dockerSrcDir.file(".env"))
properties(
"COMPOSE_PROJECT_NAME" to dockerComposeProjectName,
"KAFKATORIO_VERSION" to project.version,
"REGISTRY_HOST" to providers.gradleProperty("dockerContainerRegistryHost"),
)
}
tasks.assemble { dependsOn(dockerEnvUpdate) }
//import kafkatorio.tasks.DockerEnvUpdateTask
//import kafkatorio.extensions.DOCKER_COMPOSE_TASK_GROUP
//import org.jetbrains.kotlin.util.parseSpaceSeparatedArgs
//
//plugins {
// base
// id("kafkatorio.conventions.infra.process-runner")
//}
//
//
//val dockerComposeProjectName: String by extra(rootProject.name)
//val dockerSrcDir: Directory by extra(layout.projectDirectory.dir("src"))
//
//
//val dockerUp by tasks.registering(Exec::class) {
// group = DOCKER_COMPOSE_TASK_GROUP
//
// dependsOn(tasks.assemble)
// logging.captureStandardOutput(LogLevel.LIFECYCLE)
//
// workingDir = dockerSrcDir.asFile
// commandLine = parseSpaceSeparatedArgs(""" docker-compose up -d """)
//}
//
//
//val dockerDown by tasks.registering(Exec::class) {
// group = DOCKER_COMPOSE_TASK_GROUP
//
// logging.captureStandardOutput(LogLevel.LIFECYCLE)
//
// workingDir = dockerSrcDir.asFile
// commandLine = parseSpaceSeparatedArgs(""" docker-compose down """)
//}
//
//
//val dockerRemove by tasks.registering(Exec::class) {
// group = DOCKER_COMPOSE_TASK_GROUP
//
// logging.captureStandardOutput(LogLevel.LIFECYCLE)
//
// workingDir = dockerSrcDir.asFile
// commandLine = parseSpaceSeparatedArgs(""" docker-compose rm --stop -v -f """)
//}
//
//
//tasks.matching { it.name == "processRun" }.configureEach { dependsOn(dockerUp, dockerEnvUpdate) }
//tasks.matching { it.name == "processKill" }.configureEach { dependsOn(dockerDown) }
//
//
//val dockerEnvUpdate by tasks.registering(DockerEnvUpdateTask::class) {
// dotEnvFile.set(dockerSrcDir.file(".env"))
//
// properties(
// "COMPOSE_PROJECT_NAME" to dockerComposeProjectName,
// "KAFKATORIO_VERSION" to project.version,
// "REGISTRY_HOST" to providers.gradleProperty("dockerContainerRegistryHost"),
// )
//}
//
//
//tasks.assemble { dependsOn(dockerEnvUpdate) }

View file

@ -1,8 +1,9 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm") version embeddedKotlinVersion
`kotlin-dsl`
id("kafkatorio.conventions.kotlin-dsl")
// kotlin("jvm") version embeddedKotlinVersion
// `kotlin-dsl`
`java-gradle-plugin`
}
@ -13,17 +14,17 @@ dependencies {
implementation("org.jetbrains.kotlin:kotlin-compiler-embeddable")
}
kotlin {
jvmToolchain {
(this as JavaToolchainSpec).languageVersion.set(JavaLanguageVersion.of(gradleJvmTarget))
}
}
tasks.withType<KotlinCompile>().configureEach {
kotlinOptions {
jvmTarget = gradleJvmTarget
}
}
//kotlin {
// jvmToolchain {
// (this as JavaToolchainSpec).languageVersion.set(JavaLanguageVersion.of(gradleJvmTarget))
// }
//}
//
//tasks.withType<KotlinCompile>().configureEach {
// kotlinOptions {
// jvmTarget = gradleJvmTarget
// }
//}
gradlePlugin {
plugins {

View file

@ -1,8 +1,8 @@
package kafkatorio.tasks
package dev.adamko.geedeecee
import kafkatorio.extensions.filesChecksum
import javax.inject.Inject
import org.gradle.api.DefaultTask
import org.gradle.api.file.Directory
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.logging.LogLevel
@ -20,8 +20,16 @@ import org.gradle.api.tasks.TaskAction
import org.gradle.kotlin.dsl.property
import org.gradle.process.ExecOperations
import org.gradle.work.NormalizeLineEndings
import org.jetbrains.kotlin.incremental.md5
import org.jetbrains.kotlin.util.parseSpaceSeparatedArgs
// look into using 'build context' for up-to-date check
// https://stackoverflow.com/questions/38946683/how-to-test-dockerignore-file
// https://github.com/pwaller/docker-show-context
// https://snippets.khromov.se/see-which-files-are-included-in-your-docker-build-context/
@CacheableTask
@Suppress("UnstableApiUsage")
abstract class DockerComposeExec @Inject constructor(
@ -77,5 +85,12 @@ abstract class DockerComposeExec @Inject constructor(
companion object {
const val DOCKER_COMPOSE_GROUP: String = "docker-compose"
private fun Directory.filesChecksum(): Long = asFileTree
.files
.map { it.readBytes() + it.absolutePath.toByteArray() }
.fold(byteArrayOf()) { acc, bytes -> acc + bytes }
.md5()
}
}

View file

@ -1,7 +1,6 @@
package kafkatorio.tasks
package dev.adamko.geedeecee
import javax.inject.Inject
import kafkatorio.extensions.DOCKER_COMPOSE_TASK_GROUP
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.logging.LogLevel
import org.gradle.api.model.ObjectFactory
@ -12,14 +11,15 @@ import org.gradle.api.tasks.WriteProperties
@CacheableTask
open class DockerEnvUpdateTask @Inject constructor(
private val objects: ObjectFactory
objects: ObjectFactory
) : WriteProperties() {
@Internal
// internal and not abstract so that it can delegate to `super.outputFile` in `init {}`
val dotEnvFile: RegularFileProperty = objects.fileProperty()
init {
group = DOCKER_COMPOSE_TASK_GROUP
group = GDCPlugin.GCD_TASK_GROUP
super.setOutputFile(dotEnvFile)
super.getLogging().captureStandardOutput(LogLevel.LIFECYCLE)
}

View file

@ -0,0 +1,47 @@
package dev.adamko.geedeecee
import javax.inject.Inject
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.logging.LogLevel
import org.gradle.api.provider.ListProperty
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.TaskAction
import org.gradle.process.ExecOperations
import org.jetbrains.kotlin.util.parseSpaceSeparatedArgs
abstract class GDCCommandTask @Inject constructor(
private val executor: ExecOperations
) : DefaultTask() {
init {
super.setGroup(GDCPlugin.GCD_TASK_GROUP)
description = "Run a docker-compose command."
logging.captureStandardOutput(LogLevel.LIFECYCLE)
}
@get:InputDirectory
abstract val workingDir: DirectoryProperty
@get:Input
abstract val separatedArgs: ListProperty<String>
@TaskAction
fun dockerCompose() {
val separatedArgs: List<String> = separatedArgs.get()
executor.exec {
workingDir(this@GDCCommandTask.workingDir)
commandLine(separatedArgs)
}
}
@Suppress("FunctionName")
fun `docker-compose`(args: String) {
separatedArgs.addAll(
parseSpaceSeparatedArgs("docker-compose $args")
)
}
}

View file

@ -1,14 +1,17 @@
package dev.adamko.geedeecee
import javax.inject.Inject
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.*
import org.gradle.api.file.ProjectLayout
import org.gradle.api.provider.ProviderFactory
import org.gradle.kotlin.dsl.create
import org.gradle.api.logging.*
import org.gradle.api.provider.*
import org.gradle.api.tasks.*
import org.gradle.kotlin.dsl.*
import org.gradle.language.base.plugins.LifecycleBasePlugin
import org.jetbrains.kotlin.util.parseSpaceSeparatedArgs
@Suppress("UnstableApiUsage")
abstract class GDCPlugin @Inject constructor(
private val providers: ProviderFactory,
private val layout: ProjectLayout,
@ -16,27 +19,78 @@ abstract class GDCPlugin @Inject constructor(
override fun apply(target: Project) {
target.createSettings()
target.plugins.apply(LifecycleBasePlugin::class)
val gdcSettings = target.createSettings()
val dockerComposeEnvUpdate by target.tasks.registering(DockerEnvUpdateTask::class) {
dotEnvFile.set(gdcSettings.srcDir.file(".env"))
// properties(
// "COMPOSE_PROJECT_NAME" to gdcSettings.composeProjectName,
// "KAFKATORIO_VERSION" to gdcSettings.composeProjectVersion,
// "REGISTRY_HOST" to providers.gradleProperty("dockerContainerRegistryHost"),
// )
}
val dockerComposeUp by target.tasks.registering(GDCCommandTask::class) {
`docker-compose`("up -d")
}
val dockerComposeDown by target.tasks.registering(GDCCommandTask::class) {
`docker-compose`("down")
}
val dockerComposeStop by target.tasks.registering(GDCCommandTask::class) {
`docker-compose`("stop")
}
val dockerComposeBuild by target.tasks.registering(GDCCommandTask::class) {
`docker-compose`("build")
}
val dockerComposeRemove by target.tasks.registering(GDCCommandTask::class) {
`docker-compose`("rm --stop -v -f")
}
dockerComposeUp.configure { dependsOn(dockerComposeBuild) }
dockerComposeRemove.configure { dependsOn(dockerComposeDown) }
target.tasks.withType<GDCCommandTask>().configureEach {
workingDir.convention(gdcSettings.srcDir)
dependsOn(dockerComposeEnvUpdate)
}
val assembleTasks = target.tasks.matching { LifecycleBasePlugin.ASSEMBLE_TASK_NAME == it.name }
assembleTasks.configureEach {
dependsOn(dockerComposeEnvUpdate)
}
dockerComposeUp.configure {
dependsOn(assembleTasks)
}
}
private fun Project.createSettings(): GDCSettings =
extensions.create<GDCSettings>(GDC_EXTENSION_NAME).apply {
composeProjectName.convention(providers.provider { project.name })
composeProjectVersion.convention(providers.provider { project.name })
containerRegistryHost.convention(providers.gradleProperty("dockerContainerRegistryHost"))
srcDir.convention(layout.projectDirectory.dir("docker"))
dotEnv.put("COMPOSE_PROJECT_NAME", composeProjectName)
dotEnv.put("PROJECT_VERSION", providers.provider { project.version.toString() })
dotEnv.put("REGISTRY_HOST", providers.gradleProperty("dockerContainerRegistryHost"))
dotEnv.put("PROJECT_VERSION", composeProjectVersion)
dotEnv.put("REGISTRY_HOST", containerRegistryHost)
enabled.convention(
providers.exec {
commandLine = parseSpaceSeparatedArgs("docker info")
isIgnoreExitValue = true
}.result.map {
it.exitValue == 0
}
)
enabled.convention(isDockerActive())
}
private fun isDockerActive(): Provider<Boolean> =
providers.exec {
commandLine = parseSpaceSeparatedArgs("docker info")
isIgnoreExitValue = true
}.result.map {
it.exitValue == 0
}

View file

@ -11,6 +11,12 @@ interface GDCSettings {
@get:Input
val composeProjectName: Property<String>
@get:Input
val composeProjectVersion: Property<String>
@get:Input
val containerRegistryHost: Property<String>
@get:Internal
val srcDir: DirectoryProperty

View file

@ -1,10 +1,12 @@
import kafkatorio.conventions.overrideKotlinLanguageVersion
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
`kotlin-dsl`
kotlin("jvm") version embeddedKotlinVersion
id("kafkatorio.conventions.kotlin-dsl")
kotlin("plugin.serialization") version embeddedKotlinVersion
}
dependencies {
implementation(platform(libs.kotlin.bom))
@ -32,20 +34,42 @@ dependencies {
}
val gradleJvmTarget = "11"
//val gradleJvmTarget = 11
//
//kotlin {
// jvmToolchain(gradleJvmTarget)
//}
//
//kotlinDslPluginOptions {
// jvmTarget.set("$gradleJvmTarget")
//}
//
//tasks.withType<KotlinCompile>().configureEach {
// kotlinOptions {
// jvmTarget = "$gradleJvmTarget"
// }
//}
kotlin {
jvmToolchain {
(this as JavaToolchainSpec).languageVersion.set(JavaLanguageVersion.of(gradleJvmTarget))
gradlePlugin {
plugins {
create("factorioMod") {
displayName = "Factorio Mod"
id = "dev.adamko.factorio-mod"
implementationClass = "dev.adamko.gradle.factorio.FactorioModPlugin"
}
}
}
kotlinDslPluginOptions {
jvmTarget.set(gradleJvmTarget)
}
tasks.withType<KotlinCompile>().configureEach {
kotlinOptions {
jvmTarget = gradleJvmTarget
}
}
overrideKotlinLanguageVersion("1.6")
//val gradleKotlinTarget = "1.7"
//
//afterEvaluate {
// tasks.withType<KotlinCompile>().configureEach {
// kotlinOptions {
// apiVersion = gradleKotlinTarget
// languageVersion = gradleKotlinTarget
// }
// }
//}

View file

@ -0,0 +1,43 @@
package dev.adamko.gradle.factorio
import javax.inject.Inject
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.FileSystemOperations
import org.gradle.api.internal.file.FileOperations
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.TaskAction
abstract class FactorioModInstallTask @Inject constructor(
private val files: FileSystemOperations,
) : DefaultTask() {
@get:Internal
abstract val clientModDirectory: DirectoryProperty
@get:InputFiles
abstract val modFiles: ConfigurableFileCollection
init {
description = "Copy the mod to the Factorio client"
group = FactorioModPlugin.TASK_GROUP
super.onlyIf {
require(it is FactorioModInstallTask)
it.clientModDirectory.asFile.get().exists()
}
}
@TaskAction
fun install() {
logger.lifecycle("Copying mod from $modFiles to $clientModDirectory")
files.copy {
from(modFiles)
into(clientModDirectory)
}
}
}

View file

@ -1,4 +1,4 @@
package dev.adamko.factorio_gradle
package dev.adamko.gradle.factorio
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty

View file

@ -0,0 +1,140 @@
package dev.adamko.gradle.factorio.mod_portal
import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.defaultRequest
import io.ktor.client.plugins.logging.LogLevel
import io.ktor.client.request.forms.MultiPartFormDataContent
import io.ktor.client.request.forms.formData
import io.ktor.client.request.forms.submitForm
import io.ktor.client.request.header
import io.ktor.client.request.post
import io.ktor.client.request.setBody
import io.ktor.client.statement.bodyAsText
import io.ktor.http.ContentType
import io.ktor.http.Headers
import io.ktor.http.HttpHeaders
import io.ktor.http.Parameters
import io.ktor.http.append
import io.ktor.http.isSuccess
import io.ktor.serialization.kotlinx.json.json
import java.io.File
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.json.Json
import org.gradle.api.internal.tasks.userinput.UserInputHandler
import org.gradle.kotlin.dsl.support.useToRun
import io.ktor.client.plugins.logging.Logger as KtorLogger
import io.ktor.client.plugins.logging.Logging as KtorLogging
import org.gradle.api.logging.Logger as GradleLogger
import org.gradle.api.logging.Logging as GradleLogging
class FactorioModPortalPublishClient(
private val userInputHandler: UserInputHandler,
private val distributionZip: File,
private val modName: String,
private val modVersion: String,
private val portalApiKey: String,
private val portalUploadEndpoint: String,
private val modPortalBaseURl: String,
) {
private val logger: GradleLogger = GradleLogging.getLogger(this::class.java)
private fun client() = HttpClient(CIO) {
install(KtorLogging) {
logger = object : KtorLogger {
override fun log(message: String) {
this@FactorioModPortalPublishClient.logger.debug(message)
}
}
level = LogLevel.ALL
}
install(ContentNegotiation) {
json(Json {
prettyPrint = false
isLenient = true
})
}
defaultRequest {
header(HttpHeaders.Authorization, "Bearer $portalApiKey")
port = 443
}
followRedirects = false
expectSuccess = false
}
fun uploadMod(): Unit = runBlocking {
client().useToRun {
val initUploadResponse = initUpload()
val enteredVersion = userInputHandler
.askQuestion(
"""
|Are you sure you want to publish $modName:$modVersion?
|Enter the version number to confirm:
""".trimMargin(),
"",
)
val confirmed = enteredVersion.trim().equals(modVersion.trim(), ignoreCase = true)
if (confirmed) {
upload(initUploadResponse)
} else {
logger.lifecycle("aborting mod '$modName' upload")
}
}
}
private suspend fun HttpClient.initUpload(): InitUploadResponse.Success {
val response = submitForm(
url = portalUploadEndpoint,
formParameters = Parameters.build {
append("mod", modName)
}
)
// println(response)
val initUploadResponse: InitUploadResponse =
Json.decodeFromString(InitUploadResponse.serializer(), response.bodyAsText())
// println(initUploadResponse)
require(response.status.isSuccess()) { "init upload request failed" }
return when (initUploadResponse) {
is Failure -> error(initUploadResponse)
is InitUploadResponse.Success -> initUploadResponse
}
}
private suspend fun HttpClient.upload(initUploadResponse: InitUploadResponse.Success) {
val requestForm = formData {
append(
"file",
distributionZip.readBytes(),
Headers.build {
append(HttpHeaders.ContentType, ContentType.Application.Zip)
append(HttpHeaders.ContentDisposition, "filename=${distributionZip.name}")
}
)
}
val response = post(initUploadResponse.uploadUrl) {
setBody(MultiPartFormDataContent(requestForm))
}
val submitUploadResponse =
Json.decodeFromString(SubmitUploadResponse.serializer(), response.bodyAsText())
// logger(response)
// println(submitUploadResponse)
require(response.status.isSuccess() && submitUploadResponse is SubmitUploadResponse.Success) {
"upload request failed"
}
logger.lifecycle("Mod '$modName' uploaded successfully! ${modPortalBaseURl.removeSuffix("/")}/$modName")
}
}

View file

@ -0,0 +1,111 @@
package dev.adamko.gradle.factorio.mod_portal
import dev.adamko.gradle.factorio.FactorioModPlugin
import io.ktor.http.URLBuilder
import io.ktor.http.takeFrom
import javax.inject.Inject
import kotlinx.coroutines.runBlocking
import org.gradle.api.DefaultTask
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.internal.tasks.userinput.UserInputHandler
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
import org.gradle.api.provider.ProviderFactory
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.TaskAction
import org.gradle.kotlin.dsl.property
import org.gradle.kotlin.dsl.support.get
abstract class FactorioModPublishTask @Inject constructor(
objects: ObjectFactory,
providers: ProviderFactory,
) : DefaultTask() {
@get:InputFile
abstract val distributionZip: RegularFileProperty
@get:Input
abstract val modName: Property<String>
@get:Input
abstract val modVersion: Property<String>
// @get:Input
// abstract val factorioVersion: Property<String>
/** Bearer API key */
@get:Input
val portalApiKey: Property<String> = objects.property<String>().convention(
providers.gradleProperty("FACTORIO_MOD_PORTAL_API_KEY")
)
@get:Input
val portalApiUrl: Property<String> = objects.property<String>()
.convention("https://mods.factorio.com/api/v2/")
/**
* Base URL for linking to a mod page in the portal
*
* Example `https://mods.factorio.com/mod/my-mod-name`
*/
@get:Input
val modPortalBaseURl: Property<String> = objects.property<String>()
.convention("https://mods.factorio.com/mod/")
@get:Input
val portalUploadEndpoint: Property<String> = objects.property<String>()
.convention("mods/releases/init_upload")
init {
group = FactorioModPlugin.TASK_GROUP
}
@TaskAction
fun deploy(): Unit = runBlocking {
val userInputHandler = services.get<UserInputHandler>()
val portalUploadEndpoint = URLBuilder(portalApiUrl.get())
.takeFrom(portalUploadEndpoint.get())
.buildString()
val client = FactorioModPortalPublishClient(
userInputHandler = userInputHandler,
distributionZip = distributionZip.get().asFile,
modName = modName.get(),
modVersion = modVersion.get(),
portalApiKey = portalApiKey.get(),
portalUploadEndpoint = portalUploadEndpoint,
modPortalBaseURl = modPortalBaseURl.get(),
)
client.uploadMod()
}
// private fun validateInfoJson() {
// val modVersion = modVersion.get()
// val factorioVersion = factorioVersion.get()
//
// val distributionZip = distributionZip.get().asFile.toOkioPath()
//
// val zipContents = FileSystem.SYSTEM.openZip(distributionZip)
//
// val infoJson = zipContents.read("info.json".toPath()) {
// generateSequence { readUtf8Line() }
// }.joinToString("\n")
//
// val info: FactorioModInfo = Json.decodeFromString(FactorioModInfo.serializer(), infoJson)
//
// require(info.version == modVersion) {
// "mod version mismatch, ${info.version} vs $modVersion "
// }
//
// require(info.factorioVersion == factorioVersion) {
// "Factorio version mismatch, ${info.factorioVersion} vs $factorioVersion "
// }
// }
}

View file

@ -0,0 +1,88 @@
package dev.adamko.gradle.factorio.mod_portal
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonContentPolymorphicSerializer
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.jsonObject
@Serializable(with = InitUploadResponse.Serializer::class)
sealed interface InitUploadResponse {
/**
* @param[uploadUrl] URL the mod zip file should be uploaded to
*/
@Serializable
data class Success(
@SerialName("upload_url")
val uploadUrl: String,
) : InitUploadResponse
object Serializer :
JsonContentPolymorphicSerializer<InitUploadResponse>(InitUploadResponse::class) {
override fun selectDeserializer(element: JsonElement) = when {
"upload_url" in element.jsonObject -> Success.serializer()
else -> Failure.serializer()
}
}
}
@Serializable(with = SubmitUploadResponse.Serializer::class)
sealed interface SubmitUploadResponse {
@Serializable
data class Success(
val success: Boolean
) : SubmitUploadResponse
object Serializer : JsonContentPolymorphicSerializer<SubmitUploadResponse>(
SubmitUploadResponse::class
) {
override fun selectDeserializer(element: JsonElement) = when {
"success" in element.jsonObject -> Success.serializer()
else -> Failure.serializer()
}
}
}
@Serializable
data class Failure(
val error: String? = null,
val message: String? = null,
) : InitUploadResponse, SubmitUploadResponse {
private val reason: String?
get() = reasons[error]
override fun toString() = "Failure(error=$error, message=$message, reason=$reason)"
companion object {
private val reasons: Map<String?, String> = mapOf(
//@formatter:off
"InvalidApiKey" to "Missing or invalid API key for the current endpoint",
"InvalidRequest" to "Invalid request",
"InternalError" to "Internal error, please try again later.",
"Forbidden" to "Insufficient permission for current endpoint",
"Unknown" to "Unknown error, please try again later",
"InvalidModRelease" to "Invalid release data in info.json",
"InvalidModUpload" to "Invalid mod data in zipfile",
"UnknownMod" to "Mod does not exist in mod portal",
//@formatter:on
)
}
}
@Serializable
data class FactorioModInfo(
val name: String,
val version: String,
val title: String,
val description: String,
val author: String,
@SerialName("factorio_version") val factorioVersion: String,
val dependencies: List<String>,
)

View file

@ -1,3 +1,4 @@
import org.gradle.kotlin.dsl.support.expectedKotlinDslPluginsVersion
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
@ -11,6 +12,10 @@ dependencies {
// This project should only be providing simple plugins for configuring Gradle without
// dependencies. Try to avoid setting dependencies here, else the Gradle script classpath might
// get polluted and messy.
implementation("org.gradle.kotlin:gradle-kotlin-dsl-plugins:$expectedKotlinDslPluginsVersion")
// implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:${embeddedKotlinVersion}")
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:${libs.versions.kotlin.get()}")
}
kotlin {

View file

@ -0,0 +1,30 @@
package kafkatorio.conventions
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("org.gradle.kotlin.kotlin-dsl")
kotlin("jvm")
}
val gradleJvmTarget = 11
//kotlin {
// jvmToolchain(gradleJvmTarget)
//}
kotlin {
jvmToolchain {
(this as JavaToolchainSpec).languageVersion.set(JavaLanguageVersion.of(gradleJvmTarget))
}
}
kotlinDslPluginOptions {
jvmTarget.set("$gradleJvmTarget")
}
tasks.withType<KotlinCompile>().configureEach {
kotlinOptions {
jvmTarget = "$gradleJvmTarget"
}
}

View file

@ -0,0 +1,21 @@
package kafkatorio.conventions
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.gradle.jvm.toolchain.JavaLanguageVersion
import org.gradle.jvm.toolchain.JavaToolchainSpec
import org.gradle.kotlin.dsl.plugins.dsl.*
import org.gradle.kotlin.dsl.withType
fun Project.overrideKotlinLanguageVersion(
gradleKotlinTarget: String = "1.6"
) {
afterEvaluate {
tasks.withType<KotlinCompile>().configureEach {
kotlinOptions {
apiVersion = gradleKotlinTarget
languageVersion = gradleKotlinTarget
}
}
}
}