Unverified Commit b0dfb83d authored by Alexey Tsvetkov's avatar Alexey Tsvetkov Committed by GitHub

Extract signing to buildSrc (#391)

* Extract signing to buildSrc

* Enable stacktraces for GitHub Workflows

* Fix creating checksums & jar
parent fae467c2
...@@ -29,9 +29,9 @@ jobs: ...@@ -29,9 +29,9 @@ jobs:
- shell: bash - shell: bash
run: | run: |
cd $GITHUB_WORKSPACE/skiko cd $GITHUB_WORKSPACE/skiko
./gradlew -Pskiko.native.enabled=true jvmTest ./gradlew --stacktrace --info -Pskiko.native.enabled=true jvmTest
./gradlew -Pskiko.native.enabled=true macosX64Test ./gradlew --stacktrace --info -Pskiko.native.enabled=true macosX64Test
./gradlew -Pskiko.native.enabled=true publishToMavenLocal ./gradlew --stacktrace --info -Pskiko.native.enabled=true publishToMavenLocal
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v2
with: with:
name: test-reports-macos name: test-reports-macos
...@@ -52,8 +52,8 @@ jobs: ...@@ -52,8 +52,8 @@ jobs:
- shell: bash - shell: bash
run: | run: |
cd $GITHUB_WORKSPACE/skiko cd $GITHUB_WORKSPACE/skiko
./gradlew -Pskiko.native.enabled=true iosX64Test ./gradlew --stacktrace --info -Pskiko.native.enabled=true iosX64Test
./gradlew -Pskiko.native.enabled=true publishToMavenLocal ./gradlew --stacktrace --info -Pskiko.native.enabled=true publishToMavenLocal
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v2
with: with:
name: test-reports-macos name: test-reports-macos
...@@ -87,8 +87,8 @@ jobs: ...@@ -87,8 +87,8 @@ jobs:
sudo apt-get install ninja-build fontconfig libfontconfig1-dev libglu1-mesa-dev libxrandr-dev zip xvfb -y sudo apt-get install ninja-build fontconfig libfontconfig1-dev libglu1-mesa-dev libxrandr-dev zip xvfb -y
sudo Xvfb :0 -screen 0 1280x720x24 & sudo Xvfb :0 -screen 0 1280x720x24 &
export DISPLAY=:0 export DISPLAY=:0
./gradlew -Pskiko.native.enabled=true -i linuxX64Test jvmTest ./gradlew --stacktrace --info -Pskiko.native.enabled=true linuxX64Test jvmTest
./gradlew publishToMavenLocal ./gradlew --stacktrace --info publishToMavenLocal
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v2
with: with:
name: test-reports-linux name: test-reports-linux
...@@ -124,8 +124,8 @@ jobs: ...@@ -124,8 +124,8 @@ jobs:
./emsdk activate 2.0.29 ./emsdk activate 2.0.29
source ./emsdk_env.sh source ./emsdk_env.sh
cd .. cd ..
./gradlew -Pskiko.wasm.enabled=true jsTest ./gradlew --stacktrace --info -Pskiko.wasm.enabled=true jsTest
./gradlew -Pskiko.wasm.enabled=true publishSkikoWasmRuntimePublicationToMavenLocal ./gradlew --stacktrace --info -Pskiko.wasm.enabled=true publishSkikoWasmRuntimePublicationToMavenLocal
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v2
with: with:
name: test-reports-js name: test-reports-js
...@@ -145,8 +145,8 @@ jobs: ...@@ -145,8 +145,8 @@ jobs:
- shell: bash - shell: bash
run: | run: |
cd ./skiko cd ./skiko
./gradlew jvmTest ./gradlew --stacktrace --info jvmTest
./gradlew publishToMavenLocal ./gradlew --stacktrace --info publishToMavenLocal
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v2
with: with:
name: test-reports-windows name: test-reports-windows
......
...@@ -759,157 +759,42 @@ val generateVersion = project.tasks.register("generateVersion") { ...@@ -759,157 +759,42 @@ val generateVersion = project.tasks.register("generateVersion") {
} }
} }
fun localSign(signer: String, lib: File): File {
println("Local signing $lib as $signer")
val proc = ProcessBuilder("codesign", "-f", "-s", signer, lib.absolutePath)
.redirectOutput(ProcessBuilder.Redirect.PIPE)
.redirectError(ProcessBuilder.Redirect.PIPE)
.start()
proc.waitFor(5, TimeUnit.MINUTES)
if (proc.exitValue() != 0) {
val out = proc.inputStream.bufferedReader().readText()
val err = proc.errorStream.bufferedReader().readText()
println(out)
println(err)
throw GradleException("Cannot sign $lib: $err")
}
return lib
}
// See https://github.com/olonho/sealer.
fun sealBinary(sealer: String, lib: File) {
println("Sealing $lib by $sealer")
val proc = ProcessBuilder(sealer, "-f", lib.absolutePath, "-p", "Java_")
.redirectOutput(ProcessBuilder.Redirect.INHERIT)
.redirectError(ProcessBuilder.Redirect.INHERIT)
.start()
proc.waitFor(2, TimeUnit.MINUTES)
if (proc.exitValue() != 0) {
throw GradleException("Cannot seal $lib")
}
println("Sealed!")
}
fun remoteSignCurl(signHost: String, lib: File, out: File) {
println("Remote signing $lib on $signHost")
val user = skiko.signUser ?: error("signUser is null")
val token = skiko.signToken ?: error("signToken is null")
val cmd = """
TOKEN=`curl -fsSL --user $user:$token --url "$signHost/auth" | grep token | cut -d '"' -f4` \
&& curl --no-keepalive --http1.1 --data-binary @${lib.absolutePath} \
-H "Authorization: Bearer ${'$'}TOKEN" \
-H "Content-Type:application/x-mac-app-bin" \
"$signHost/sign?name=${lib.name}" -o "${out.absolutePath}"
""".trimIndent()
val proc = ProcessBuilder("bash", "-c", cmd)
.redirectOutput(ProcessBuilder.Redirect.PIPE)
.redirectError(ProcessBuilder.Redirect.PIPE)
.start()
proc.waitFor(5, TimeUnit.MINUTES)
if (proc.exitValue() != 0) {
val out = proc.inputStream.bufferedReader().readText()
val err = proc.errorStream.bufferedReader().readText()
println(out)
println(err)
throw GradleException("Cannot sign $lib: $err")
} else {
val outSize = out.length()
if (outSize < 200 * 1024) {
val content = out.readText()
println(content)
throw GradleException("Output is too short $outSize: ${content.take(200)}...")
}
}
}
fun remoteSignCodesign(fileToSign: File) {
val user = skiko.signUser ?: error("signUser is null")
val token = skiko.signToken ?: error("signToken is null")
val cmd = arrayOf(
projectDir.resolve("tools/codesign-client-darwin-x64").absolutePath,
fileToSign.absolutePath
)
val procBuilder = ProcessBuilder(*cmd).apply {
directory(fileToSign.parentFile)
val env = environment()
env["SERVICE_ACCOUNT_NAME"] = user
env["SERVICE_ACCOUNT_TOKEN"] = token
redirectOutput(ProcessBuilder.Redirect.INHERIT)
redirectError(ProcessBuilder.Redirect.INHERIT)
}
logger.info("Starting remote code sign")
val proc = procBuilder.start()
proc.waitFor(5, TimeUnit.MINUTES)
if (proc.exitValue() != 0) {
throw GradleException("Failed to sign $fileToSign")
} else {
val signedDir = fileToSign.parentFile.resolve("signed")
val signedFile = signedDir.resolve(fileToSign.name)
check(signedFile.exists()) {
buildString {
appendLine("Signed file does not exist: $signedFile")
appendLine("Other files in $signedDir:")
signedDir.list()?.let { names ->
names.forEach {
appendLine(" * $it")
}
}
}
}
val size = signedFile.length()
if (size < 200 * 1024) {
val content = signedFile.readText()
println(content)
throw GradleException("Output is too short $size: ${content.take(200)}...")
} else {
signedFile.copyTo(fileToSign, overwrite = true)
signedFile.delete()
logger.info("Successfully signed $fileToSign")
}
}
}
val skikoJvmJar: Provider<Jar> by tasks.registering(Jar::class) { val skikoJvmJar: Provider<Jar> by tasks.registering(Jar::class) {
archiveBaseName.set("skiko-jvm") archiveBaseName.set("skiko-jvm")
from(kotlin.jvm().compilations["main"].output.allOutputs) from(kotlin.jvm().compilations["main"].output.allOutputs)
} }
val maybeSign by project.tasks.registering { val maybeSign by project.tasks.registering(SealAndSignSharedLibraryTask::class) {
dependsOn(linkJvmBindings) dependsOn(linkJvmBindings)
val lib = linkJvmBindings.map { task -> val linkOutputFile = linkJvmBindings.map { task ->
task.outDir.get().asFile.walk().single { file -> file.name.endsWith(targetOs.dynamicLibExt) } task.outDir.get().asFile.walk().single { it.name.endsWith(targetOs.dynamicLibExt) }.absoluteFile
} }
inputs.files(lib) libFile.set(project.layout.file(linkOutputFile))
outDir.set(project.layout.buildDirectory.dir("maybe-signed"))
val outputDir = project.layout.buildDirectory.dir("maybe-signed")
val output = outputDir.map { it.asFile.resolve(lib.get().name) } val toolsDir = project.layout.projectDirectory.dir("tools")
outputs.files(output) if (targetOs == OS.Linux) {
// Linux requires additional sealing to run on wider set of platforms.
doLast { // See https://github.com/olonho/sealer.
outputDir.get().asFile.apply { when (targetArch) {
deleteRecursively() Arch.X64 -> sealer.set(toolsDir.file("sealer-x64"))
mkdirs() Arch.Arm64 -> sealer.set(toolsDir.file("sealer-arm64"))
else -> error("Unexpected combination of '$targetArch' and '$targetOs'")
} }
}
val libFile = lib.get() if (hostOs == OS.MacOS) {
val outputFile = output.get() codesignClient.set(toolsDir.file("codesign-client-darwin-x64"))
libFile.copyTo(outputFile, overwrite = true)
if (targetOs == OS.Linux) {
// Linux requires additional sealing to run on wider set of platforms.
val sealer = "$projectDir/tools/sealer-${hostArch.id}"
sealBinary(sealer, outputFile)
}
if (skiko.signHost != null) {
remoteSignCodesign(outputFile)
}
} }
signHost.set(skiko.signHost)
signUser.set(skiko.signUser)
signToken.set(skiko.signToken)
} }
val createChecksums by project.tasks.registering(org.gradle.crypto.checksum.Checksum::class) { val createChecksums by project.tasks.registering(org.gradle.crypto.checksum.Checksum::class) {
dependsOn(maybeSign) dependsOn(maybeSign)
files = project.files(maybeSign.map { it.outputs.files }) + files = project.files(maybeSign.flatMap { it.outputFiles }) +
if (targetOs.isWindows) files(skiaJvmBindingsDir.map { it.resolve("${skiaBinSubdir}/icudtl.dat") }) else files() if (targetOs.isWindows) files(skiaJvmBindingsDir.map { it.resolve("${skiaBinSubdir}/icudtl.dat") }) else files()
algorithm = Checksum.Algorithm.SHA256 algorithm = Checksum.Algorithm.SHA256
outputDir = file("$buildDir/checksums") outputDir = file("$buildDir/checksums")
...@@ -919,7 +804,7 @@ val skikoJvmRuntimeJar by project.tasks.registering(Jar::class) { ...@@ -919,7 +804,7 @@ val skikoJvmRuntimeJar by project.tasks.registering(Jar::class) {
dependsOn(createChecksums) dependsOn(createChecksums)
archiveBaseName.set("skiko-$target") archiveBaseName.set("skiko-$target")
from(skikoJvmJar.map { zipTree(it.archiveFile) }) from(skikoJvmJar.map { zipTree(it.archiveFile) })
from(maybeSign.map { it.outputs.files }) from(maybeSign.flatMap { it.outputFiles })
if (targetOs.isWindows) { if (targetOs.isWindows) {
from(files(skiaJvmBindingsDir.map { it.resolve("${skiaBinSubdir}/icudtl.dat") })) from(files(skiaJvmBindingsDir.map { it.resolve("${skiaBinSubdir}/icudtl.dat") }))
} }
...@@ -1119,11 +1004,6 @@ if (hostOs == OS.Linux && hostArch != Arch.X64) { ...@@ -1119,11 +1004,6 @@ if (hostOs == OS.Linux && hostArch != Arch.X64) {
} }
} }
fun Task.projectDirs(vararg relativePaths: String): List<Directory> {
val projectDir = project.layout.projectDirectory
return relativePaths.map { path -> projectDir.dir(path) }
}
/** /**
* Do not call inside tasks.register or tasks.call callback * Do not call inside tasks.register or tasks.call callback
* (tasks' registration during other task's registration is prohibited) * (tasks' registration during other task's registration is prohibited)
......
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.FileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.internal.file.FileOperations
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.*
import java.io.File
import java.util.concurrent.TimeUnit
import javax.inject.Inject
abstract class SealAndSignSharedLibraryTask : DefaultTask() {
@get:Inject
protected abstract val fileOperations: FileOperations
@get:Optional
@get:InputFile
abstract val sealer: RegularFileProperty
@get:Optional
@get:InputFile
abstract val codesignClient: RegularFileProperty
@get:InputFile
abstract val libFile: RegularFileProperty
@get:OutputDirectory
abstract val outDir: DirectoryProperty
@get:Internal
val outputFiles: Provider<List<File>>
get() = outDir.map { outDir ->
outDir.asFile.listFiles().orEmpty().filter { it.isFile }
}
@get:Optional
@get:Input
abstract val signHost: Property<String>
@get:Optional
@get:Input
abstract val signUser: Property<String>
@get:Optional
@get:Input
abstract val signToken: Property<String>
@TaskAction
fun run() {
val outDir = outDir.get().asFile
fileOperations.delete(outDir)
fileOperations.mkdir(outDir)
val libFile = libFile.get().asFile
val outputFile = outDir.resolve(libFile.name)
libFile.copyTo(outputFile, overwrite = true)
sealer.orNull?.let { sealer ->
sealBinary(sealer.asFile, libFile)
}
val signHost = signHost.orNull
if (signHost == null) {
logger.info("Skipping signing, because 'signHost' is not set")
} else {
remoteSignCodesign(outputFile)
}
}
// See https://github.com/olonho/sealer.
private fun sealBinary(sealer: File, lib: File) {
logger.info("Sealing $lib by $sealer")
val proc = ProcessBuilder(sealer.absolutePath, "-f", lib.absolutePath, "-p", "Java_")
.redirectOutput(ProcessBuilder.Redirect.INHERIT)
.redirectError(ProcessBuilder.Redirect.INHERIT)
.start()
proc.waitFor(2, TimeUnit.MINUTES)
if (proc.exitValue() != 0) {
throw GradleException("Cannot seal $lib")
}
logger.info("Sealed!")
}
private fun remoteSignCodesign(fileToSign: File) {
val user = signUser.orNull ?: error("signUser is null")
val token = signToken.orNull ?: error("signToken is null")
val cmd = arrayOf(
codesignClient.get().asFile.absolutePath,
fileToSign.absolutePath
)
val procBuilder = ProcessBuilder(*cmd).apply {
directory(fileToSign.parentFile)
val env = environment()
env["SERVICE_ACCOUNT_NAME"] = user
env["SERVICE_ACCOUNT_TOKEN"] = token
redirectOutput(ProcessBuilder.Redirect.INHERIT)
redirectError(ProcessBuilder.Redirect.INHERIT)
}
logger.info("Starting remote code sign")
val proc = procBuilder.start()
proc.waitFor(5, TimeUnit.MINUTES)
if (proc.exitValue() != 0) {
throw GradleException("Failed to sign $fileToSign")
} else {
val signedDir = fileToSign.resolveSibling("signed")
val signedFile = signedDir.resolve(fileToSign.name)
check(signedFile.exists()) {
buildString {
appendLine("Signed file does not exist: $signedFile")
appendLine("Other files in $signedDir:")
signedDir.list()?.let { names ->
names.forEach {
appendLine(" * $it")
}
}
}
}
val size = signedFile.length()
if (size < 200 * 1024) {
val content = signedFile.readText()
logger.info(content)
throw GradleException("Output is too short $size: ${content.take(200)}...")
} else {
signedFile.copyTo(fileToSign, overwrite = true)
signedDir.deleteRecursively()
logger.info("Successfully signed $fileToSign")
}
}
}
}
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.provider.Provider
import java.io.File import java.io.File
enum class OS( enum class OS(
......
import org.gradle.api.Task import org.gradle.api.Task
import org.gradle.api.file.Directory
import org.gradle.api.provider.Provider import org.gradle.api.provider.Provider
import org.gradle.api.tasks.TaskContainer import org.gradle.api.tasks.TaskContainer
import org.gradle.kotlin.dsl.register import org.gradle.kotlin.dsl.register
...@@ -19,4 +20,9 @@ fun joinToTitleCamelCase(vararg parts: String): String = ...@@ -19,4 +20,9 @@ fun joinToTitleCamelCase(vararg parts: String): String =
parts.joinToString(separator = "") { toTitleCase(it) } parts.joinToString(separator = "") { toTitleCase(it) }
fun toTitleCase(s: String): String = fun toTitleCase(s: String): String =
s.capitalize(Locale.ROOT) s.capitalize(Locale.ROOT)
\ No newline at end of file
fun Task.projectDirs(vararg relativePaths: String): List<Directory> {
val projectDir = project.layout.projectDirectory
return relativePaths.map { path -> projectDir.dir(path) }
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment