From d37675f5c26bd299b93aae6b491f5fc5ef0f2358 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Fri, 5 Feb 2021 17:38:27 +0800 Subject: [PATCH] GPG sign for artifacts --- .gitignore | 5 +- build.gradle.kts | 1 + buildSrc/src/main/kotlin/GpgSigner.kt | 100 +++++++++++++++++ buildSrc/src/main/kotlin/JvmPublishing.kt | 1 + buildSrc/src/main/kotlin/MppPublishing.kt | 8 +- buildSrc/src/main/kotlin/PublishingGpgSign.kt | 102 ++++++++++++++++++ buildSrc/src/main/kotlin/Versions.kt | 2 +- 7 files changed, 215 insertions(+), 4 deletions(-) create mode 100644 buildSrc/src/main/kotlin/GpgSigner.kt create mode 100644 buildSrc/src/main/kotlin/PublishingGpgSign.kt diff --git a/.gitignore b/.gitignore index 9315f8357..c1d9012a0 100644 --- a/.gitignore +++ b/.gitignore @@ -45,4 +45,7 @@ keys.properties token.txt bintray.user.txt -bintray.key.txt \ No newline at end of file +bintray.key.txt + +# For gpg sign +/build-gpg-sign diff --git a/build.gradle.kts b/build.gradle.kts index a166695cf..b3f335cc8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -64,6 +64,7 @@ configure { } project.ext.set("isAndroidSDKAvailable", false) +GpgSigner.setup(project) tasks.register("publishMiraiCoreArtifactsToMavenLocal") { group = "mirai" diff --git a/buildSrc/src/main/kotlin/GpgSigner.kt b/buildSrc/src/main/kotlin/GpgSigner.kt new file mode 100644 index 000000000..4aa623a5d --- /dev/null +++ b/buildSrc/src/main/kotlin/GpgSigner.kt @@ -0,0 +1,100 @@ +/* + * Copyright 2019-2021 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + + +import org.gradle.api.Project +import java.io.File + +open class GpgSigner(private val workdir: File) { + private val workdirParent by lazy { workdir.parentFile ?: error("Assertion error: No parent file of $workdir") } + private val workdirName by lazy { workdir.name } + + fun verbose(msg: String) { + println("[GPG SIGN] [Verbose] $msg") + } + + @Suppress("RemoveExplicitTypeArguments") + private val verbosePrintOnce by lazy { + verbose("GPG Signer working dir: $workdir") + verbose("GPG command working dir: $workdirParent") + } + + constructor(workdir: String) : this(File(workdir)) + + object NoopSigner : GpgSigner("build/gpg-noop") { + override fun processGpg(vararg cmds: String) { + } + + override fun importKey(file: File) { + } + + override fun doSign(file: File) { + } + } + + companion object { + private var initialized: Boolean = false + var signer: GpgSigner = NoopSigner + fun setup(project: Project) { + if (initialized) return + initialized = true + val rootProject = project.rootProject + val gpg = rootProject.projectDir.resolve("build-gpg-sign") + gpg.mkdirs() + val keyFile = gpg.resolve("keys.gpg") + if (keyFile.isFile) { + val homedir = gpg.resolve("homedir") + signer = GpgSigner(homedir.absolutePath) + if (!homedir.resolve("pubring.kbx").isFile) { + signer.importKey(keyFile) + } + } else { + rootProject.logger.warn("GPG Key not found.") + rootProject.logger.warn("GPG Signer will not setup") + rootProject.logger.warn("Key file location: $keyFile") + } + } + } + + open fun processGpg( + vararg cmds: String + ) { + workdir.mkdirs() + verbosePrintOnce + + val response = ProcessBuilder().command(ArrayList().apply { + add("gpg") + add("--homedir"); add(workdirName) + addAll(cmds) + }.also { + verbose("Processing " + it.joinToString(" ")) + }).directory(workdirParent) + .inheritIO() + .start() + .waitFor() + if (response != 0) { + error("Exit Response $response") + } + } + + open fun importKey(file: File) { + processGpg("--batch", "--import", file.toString()) + } + + open fun doSign(file: File) { + if (!file.isFile) { + println("[GPG SIGN] $file not a file") + return + } + println("[GPG SIGN] Signing $file") + File("${file.path}.asc").delete() + processGpg("-a", "--batch", "--no-tty", "--sign", file.toString()) + } + +} diff --git a/buildSrc/src/main/kotlin/JvmPublishing.kt b/buildSrc/src/main/kotlin/JvmPublishing.kt index 52f42e365..c1e2f2059 100644 --- a/buildSrc/src/main/kotlin/JvmPublishing.kt +++ b/buildSrc/src/main/kotlin/JvmPublishing.kt @@ -115,5 +115,6 @@ inline fun Project.configurePublishing( artifact(sourcesJar.get()) } } + configGpgSign(this@configurePublishing) } } \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/MppPublishing.kt b/buildSrc/src/main/kotlin/MppPublishing.kt index eff90e5ea..2238f5cf5 100644 --- a/buildSrc/src/main/kotlin/MppPublishing.kt +++ b/buildSrc/src/main/kotlin/MppPublishing.kt @@ -42,10 +42,13 @@ fun Project.configureMppPublishing() { .forEach { publication -> val moduleFile = buildDir.resolve("publications/${publication.name}/module.json") if (moduleFile.exists()) { - publication.artifact(object : + val artifact = (object : org.gradle.api.publish.maven.internal.artifact.FileBasedMavenArtifact(moduleFile) { override fun getDefaultExtension() = "module" }) + publication.artifact(artifact) + GpgSigner.signer.doSign(moduleFile) + publication.artifact(GPGSignMavenArtifact(artifact)) } } } @@ -86,6 +89,7 @@ fun Project.configureMppPublishing() { } } } + configGpgSign(this@configureMppPublishing) } } } @@ -129,6 +133,6 @@ val publishPlatformArtifactsInRootModule: Project.(MavenPublication) -> Unit = { } } -private fun MavenArtifact.smartToString(): String { +public fun MavenArtifact.smartToString(): String { return "${file.path}, classifier=${classifier}, ext=${extension}" } diff --git a/buildSrc/src/main/kotlin/PublishingGpgSign.kt b/buildSrc/src/main/kotlin/PublishingGpgSign.kt new file mode 100644 index 000000000..e0a52c3c4 --- /dev/null +++ b/buildSrc/src/main/kotlin/PublishingGpgSign.kt @@ -0,0 +1,102 @@ +/* + * Copyright 2019-2021 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +import org.gradle.api.Project +import org.gradle.api.internal.tasks.DefaultTaskDependency +import org.gradle.api.internal.tasks.TaskDependencyInternal +import org.gradle.api.publish.PublishingExtension +import org.gradle.api.publish.maven.MavenArtifact +import org.gradle.api.publish.maven.MavenPublication +import org.gradle.api.publish.maven.internal.artifact.AbstractMavenArtifact +import org.gradle.api.publish.maven.internal.publication.DefaultMavenPublication +import java.io.File + +open class GPGSignMavenArtifact( + private val delegate: MavenArtifact, + private val tasks: TaskDependencyInternal = TaskDependencyInternal.EMPTY +) : AbstractMavenArtifact() { + override fun getFile(): File { + return File(delegate.file.path + ".asc") + } + + override fun shouldBePublished(): Boolean = (delegate as? AbstractMavenArtifact)?.shouldBePublished() ?: true + override fun getDefaultExtension(): String = delegate.extension + ".asc" + override fun getDefaultClassifier(): String = delegate.classifier ?: "" + override fun getDefaultBuildDependencies(): TaskDependencyInternal = tasks +} + +class NameCounter(val name: String) { + var counter = 0 + val nextName: String + get() = name + if (counter == 0) { + counter = 1; "" + } else { + counter++; counter + } +} + +object PublishingAccess { + fun getMetadataArtifacts(publication: MavenPublication): Collection { + if (publication is DefaultMavenPublication) { + return DefaultMavenPublication::class.java.getDeclaredField("metadataArtifacts") + .also { it.isAccessible = true } + .get(publication) as Collection + } + return emptyList() + } +} + +fun PublishingExtension.configGpgSign(project: Project) { + if (GpgSigner.signer === GpgSigner.NoopSigner) { + return + } + val tasks = DefaultTaskDependency() + val signArtifactsGPG = NameCounter("signArtifactsGPG") + + publications.forEach { publication -> + if (publication is MavenPublication) { + val artifacts0: Collection, (MavenArtifact) -> Unit>> = listOf( + publication.artifacts to { publication.artifact(it) }, // main artifacts + PublishingAccess.getMetadataArtifacts(publication).let { artifacts -> // pom files + if (artifacts is MutableCollection) { + artifacts to { artifacts.add(it) } + } else { + artifacts to { publication.artifact(it) } + } + } + ) + val allArtifacts = artifacts0.flatMap { it.first }.toList() + + if (allArtifacts.isNotEmpty()) { + tasks.add(project.tasks.create(signArtifactsGPG.nextName) { + group = "publishing" + doLast { + allArtifacts.forEach { artifact -> + if ((artifact as? AbstractMavenArtifact)?.shouldBePublished() != false) { + GpgSigner.signer.doSign(artifact.file) + } + } + } + + allArtifacts.forEach { + dependsOn(it.buildDependencies) + } + }) + + artifacts0.forEach { (artifacts, artifactsRegister) -> + artifacts.toList().forEach { artifact -> + logPublishing("gpg sign for artifact ${artifact.smartToString()}") + artifactsRegister(GPGSignMavenArtifact(artifact, tasks)) + } + } + } + } + } + +} diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index a6c7824fd..83e5a0200 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -12,7 +12,7 @@ import org.gradle.api.attributes.Attribute object Versions { - const val project = "2.3.2" + const val project = "2.3.2-dev-publish-1" const val core = project const val console = project