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:
- shell: bash
run: |
cd $GITHUB_WORKSPACE/skiko
./gradlew -Pskiko.native.enabled=true jvmTest
./gradlew -Pskiko.native.enabled=true macosX64Test
./gradlew -Pskiko.native.enabled=true publishToMavenLocal
./gradlew --stacktrace --info -Pskiko.native.enabled=true jvmTest
./gradlew --stacktrace --info -Pskiko.native.enabled=true macosX64Test
./gradlew --stacktrace --info -Pskiko.native.enabled=true publishToMavenLocal
- uses: actions/upload-artifact@v2
with:
name: test-reports-macos
......@@ -52,8 +52,8 @@ jobs:
- shell: bash
run: |
cd $GITHUB_WORKSPACE/skiko
./gradlew -Pskiko.native.enabled=true iosX64Test
./gradlew -Pskiko.native.enabled=true publishToMavenLocal
./gradlew --stacktrace --info -Pskiko.native.enabled=true iosX64Test
./gradlew --stacktrace --info -Pskiko.native.enabled=true publishToMavenLocal
- uses: actions/upload-artifact@v2
with:
name: test-reports-macos
......@@ -87,8 +87,8 @@ jobs:
sudo apt-get install ninja-build fontconfig libfontconfig1-dev libglu1-mesa-dev libxrandr-dev zip xvfb -y
sudo Xvfb :0 -screen 0 1280x720x24 &
export DISPLAY=:0
./gradlew -Pskiko.native.enabled=true -i linuxX64Test jvmTest
./gradlew publishToMavenLocal
./gradlew --stacktrace --info -Pskiko.native.enabled=true linuxX64Test jvmTest
./gradlew --stacktrace --info publishToMavenLocal
- uses: actions/upload-artifact@v2
with:
name: test-reports-linux
......@@ -124,8 +124,8 @@ jobs:
./emsdk activate 2.0.29
source ./emsdk_env.sh
cd ..
./gradlew -Pskiko.wasm.enabled=true jsTest
./gradlew -Pskiko.wasm.enabled=true publishSkikoWasmRuntimePublicationToMavenLocal
./gradlew --stacktrace --info -Pskiko.wasm.enabled=true jsTest
./gradlew --stacktrace --info -Pskiko.wasm.enabled=true publishSkikoWasmRuntimePublicationToMavenLocal
- uses: actions/upload-artifact@v2
with:
name: test-reports-js
......@@ -145,8 +145,8 @@ jobs:
- shell: bash
run: |
cd ./skiko
./gradlew jvmTest
./gradlew publishToMavenLocal
./gradlew --stacktrace --info jvmTest
./gradlew --stacktrace --info publishToMavenLocal
- uses: actions/upload-artifact@v2
with:
name: test-reports-windows
......
......@@ -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) {
archiveBaseName.set("skiko-jvm")
from(kotlin.jvm().compilations["main"].output.allOutputs)
}
val maybeSign by project.tasks.registering {
val maybeSign by project.tasks.registering(SealAndSignSharedLibraryTask::class) {
dependsOn(linkJvmBindings)
val lib = linkJvmBindings.map { task ->
task.outDir.get().asFile.walk().single { file -> file.name.endsWith(targetOs.dynamicLibExt) }
}
inputs.files(lib)
val outputDir = project.layout.buildDirectory.dir("maybe-signed")
val output = outputDir.map { it.asFile.resolve(lib.get().name) }
outputs.files(output)
doLast {
outputDir.get().asFile.apply {
deleteRecursively()
mkdirs()
val linkOutputFile = linkJvmBindings.map { task ->
task.outDir.get().asFile.walk().single { it.name.endsWith(targetOs.dynamicLibExt) }.absoluteFile
}
libFile.set(project.layout.file(linkOutputFile))
outDir.set(project.layout.buildDirectory.dir("maybe-signed"))
val libFile = lib.get()
val outputFile = output.get()
libFile.copyTo(outputFile, overwrite = true)
val toolsDir = project.layout.projectDirectory.dir("tools")
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)
// See https://github.com/olonho/sealer.
when (targetArch) {
Arch.X64 -> sealer.set(toolsDir.file("sealer-x64"))
Arch.Arm64 -> sealer.set(toolsDir.file("sealer-arm64"))
else -> error("Unexpected combination of '$targetArch' and '$targetOs'")
}
if (skiko.signHost != null) {
remoteSignCodesign(outputFile)
}
if (hostOs == OS.MacOS) {
codesignClient.set(toolsDir.file("codesign-client-darwin-x64"))
}
signHost.set(skiko.signHost)
signUser.set(skiko.signUser)
signToken.set(skiko.signToken)
}
val createChecksums by project.tasks.registering(org.gradle.crypto.checksum.Checksum::class) {
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()
algorithm = Checksum.Algorithm.SHA256
outputDir = file("$buildDir/checksums")
......@@ -919,7 +804,7 @@ val skikoJvmRuntimeJar by project.tasks.registering(Jar::class) {
dependsOn(createChecksums)
archiveBaseName.set("skiko-$target")
from(skikoJvmJar.map { zipTree(it.archiveFile) })
from(maybeSign.map { it.outputs.files })
from(maybeSign.flatMap { it.outputFiles })
if (targetOs.isWindows) {
from(files(skiaJvmBindingsDir.map { it.resolve("${skiaBinSubdir}/icudtl.dat") }))
}
......@@ -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
* (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.provider.Provider
import java.io.File
enum class OS(
......
import org.gradle.api.Task
import org.gradle.api.file.Directory
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.TaskContainer
import org.gradle.kotlin.dsl.register
......@@ -20,3 +21,8 @@ fun joinToTitleCamelCase(vararg parts: String): String =
fun toTitleCase(s: String): String =
s.capitalize(Locale.ROOT)
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