Alexey Tsvetkov
4 years ago
committed by
Alexey Tsvetkov
14 changed files with 344 additions and 159 deletions
@ -0,0 +1,89 @@
|
||||
/* |
||||
* Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. |
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||
*/ |
||||
|
||||
package org.jetbrains.compose.desktop.application.internal |
||||
|
||||
import org.gradle.api.file.Directory |
||||
import org.gradle.api.provider.Property |
||||
import org.gradle.api.provider.Provider |
||||
import org.gradle.process.ExecOperations |
||||
import org.gradle.process.ExecResult |
||||
import java.io.File |
||||
import java.time.LocalDateTime |
||||
import java.time.format.DateTimeFormatter |
||||
|
||||
internal class ExternalToolRunner( |
||||
private val verbose: Property<Boolean>, |
||||
private val logsDir: Provider<Directory>, |
||||
private val execOperations: ExecOperations |
||||
) { |
||||
operator fun invoke( |
||||
tool: File, |
||||
args: Collection<String>, |
||||
environment: Map<String, Any> = emptyMap(), |
||||
workingDir: File? = null, |
||||
checkExitCodeIsNormal: Boolean = true, |
||||
processStdout: Function1<String, Unit>? = null, |
||||
forceLogToFile: Boolean = false |
||||
): ExecResult { |
||||
val logsDir = logsDir.ioFile |
||||
logsDir.mkdirs() |
||||
|
||||
val toolName = tool.nameWithoutExtension |
||||
val logToConsole = verbose.get() && !forceLogToFile |
||||
val outFile = logsDir.resolve("${toolName}-${currentTimeStamp()}-out.txt") |
||||
val errFile = logsDir.resolve("${toolName}-${currentTimeStamp()}-err.txt") |
||||
|
||||
val result = outFile.outputStream().buffered().use { outFileStream -> |
||||
errFile.outputStream().buffered().use { errFileStream -> |
||||
execOperations.exec { spec -> |
||||
spec.executable = tool.absolutePath |
||||
spec.args(*args.toTypedArray()) |
||||
workingDir?.let { wd -> spec.workingDir(wd) } |
||||
spec.environment(environment) |
||||
// check exit value later |
||||
spec.isIgnoreExitValue = true |
||||
|
||||
if (logToConsole) { |
||||
spec.standardOutput = spec.standardOutput.alsoOutputTo(outFileStream) |
||||
spec.errorOutput = spec.errorOutput.alsoOutputTo(errFileStream) |
||||
} else { |
||||
spec.standardOutput = outFileStream |
||||
spec.errorOutput = errFileStream |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (checkExitCodeIsNormal && result.exitValue != 0) { |
||||
val errMsg = buildString { |
||||
appendLine("External tool execution failed:") |
||||
val cmd = (listOf(tool.absolutePath) + args).joinToString(", ") |
||||
appendLine("* Command: [$cmd]") |
||||
appendLine("* Working dir: [${workingDir?.absolutePath.orEmpty()}]") |
||||
appendLine("* Exit code: ${result.exitValue}") |
||||
appendLine("* Standard output log: ${outFile.absolutePath}") |
||||
appendLine("* Error log: ${errFile.absolutePath}") |
||||
} |
||||
|
||||
error(errMsg) |
||||
} |
||||
|
||||
if (processStdout != null) { |
||||
processStdout(outFile.readText()) |
||||
} |
||||
|
||||
if (result.exitValue == 0) { |
||||
outFile.delete() |
||||
errFile.delete() |
||||
} |
||||
|
||||
return result |
||||
} |
||||
|
||||
private fun currentTimeStamp() = |
||||
LocalDateTime.now() |
||||
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss")) |
||||
} |
@ -0,0 +1,73 @@
|
||||
/* |
||||
* Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. |
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||
*/ |
||||
|
||||
package org.jetbrains.compose.desktop.application.internal |
||||
|
||||
import org.jetbrains.compose.desktop.application.internal.validation.ValidatedMacOSSigningSettings |
||||
import java.io.File |
||||
import java.util.regex.Pattern |
||||
|
||||
internal class MacSigner( |
||||
val settings: ValidatedMacOSSigningSettings, |
||||
private val runExternalTool: ExternalToolRunner |
||||
) { |
||||
private lateinit var signKey: String |
||||
|
||||
init { |
||||
runExternalTool( |
||||
MacUtils.security, |
||||
args = listOfNotNull( |
||||
"find-certificate", |
||||
"-a", |
||||
"-c", |
||||
settings.fullDeveloperID, |
||||
settings.keychain?.absolutePath |
||||
), |
||||
processStdout = { stdout -> |
||||
signKey = findCertificate(stdout) |
||||
} |
||||
) |
||||
} |
||||
|
||||
fun sign(file: File) { |
||||
val args = arrayListOf( |
||||
"-vvvv", |
||||
"--timestamp", |
||||
"--options", "runtime", |
||||
"--force", |
||||
"--prefix", settings.prefix, |
||||
"--sign", signKey |
||||
) |
||||
|
||||
settings.keychain?.let { |
||||
args.add("--keychain") |
||||
args.add(it.absolutePath) |
||||
} |
||||
|
||||
args.add(file.absolutePath) |
||||
|
||||
runExternalTool(MacUtils.codesign, args) |
||||
} |
||||
|
||||
private fun findCertificate(certificates: String): String { |
||||
val regex = Pattern.compile("\"alis\"<blob>=\"([^\"]+)\"") |
||||
val m = regex.matcher(certificates) |
||||
if (!m.find()) { |
||||
val keychainPath = settings.keychain?.absolutePath |
||||
error( |
||||
"Could not find certificate for '${settings.identity}'" + |
||||
" in keychain [${keychainPath.orEmpty()}]" |
||||
) |
||||
} |
||||
|
||||
val result = m.group(1) |
||||
if (m.find()) |
||||
error( |
||||
"Multiple matching certificates are found for '${settings.fullDeveloperID}'. " + |
||||
"Please specify keychain containing unique matching certificate." |
||||
) |
||||
return result |
||||
} |
||||
} |
@ -0,0 +1,49 @@
|
||||
/* |
||||
* Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. |
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||
*/ |
||||
|
||||
package org.jetbrains.compose.test |
||||
|
||||
import java.io.File |
||||
|
||||
internal data class ProcessRunResult(val exitCode: Int, val out: String, val err: String) |
||||
|
||||
internal fun runProcess( |
||||
tool: File, |
||||
args: Collection<String>, |
||||
checkExitCodeIsNormal: Boolean = true |
||||
): ProcessRunResult { |
||||
val outFile = File.createTempFile("run-process-compose-tests-out.txt", null).apply { deleteOnExit() } |
||||
val errFile = File.createTempFile("run-process-compose-tests-err.txt", null).apply { deleteOnExit() } |
||||
return try { |
||||
val cmd = arrayOf(tool.absolutePath, *args.toTypedArray()) |
||||
val process = ProcessBuilder().run { |
||||
redirectError(errFile) |
||||
redirectOutput(outFile) |
||||
command(*cmd) |
||||
start() |
||||
} |
||||
val exitCode = process.waitFor() |
||||
if (checkExitCodeIsNormal) { |
||||
check(exitCode == 0) { |
||||
buildString { |
||||
appendLine("Non-zero exit code: $exitCode") |
||||
appendLine("Command: ${cmd.joinToString(", ")}") |
||||
appendLine("Out:") |
||||
outFile.forEachLine { line -> |
||||
appendLine(" >$line") |
||||
} |
||||
appendLine("Err:") |
||||
errFile.forEachLine { line -> |
||||
appendLine(" >$line") |
||||
} |
||||
} |
||||
} |
||||
} |
||||
ProcessRunResult(exitCode = exitCode, out = outFile.readText(), err = errFile.readText()) |
||||
} finally { |
||||
outFile.delete() |
||||
errFile.delete() |
||||
} |
||||
} |
@ -0,0 +1,37 @@
|
||||
import org.jetbrains.compose.desktop.application.dsl.TargetFormat |
||||
|
||||
plugins { |
||||
id "org.jetbrains.kotlin.jvm" |
||||
id "org.jetbrains.compose" |
||||
} |
||||
|
||||
repositories { |
||||
google() |
||||
mavenCentral() |
||||
maven { |
||||
url "https://maven.pkg.jetbrains.space/public/p/compose/dev" |
||||
} |
||||
} |
||||
|
||||
dependencies { |
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib" |
||||
implementation compose.desktop.currentOs |
||||
} |
||||
|
||||
compose.desktop { |
||||
application { |
||||
mainClass = "MainKt" |
||||
nativeDistributions { |
||||
packageName = "TestPackage" |
||||
macOS { |
||||
bundleID = "signing.test.package" |
||||
|
||||
signing { |
||||
sign.set(true) |
||||
identity.set("Compose Test") |
||||
keychain.set(project.file("compose.test.keychain").absolutePath) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
Binary file not shown.
@ -0,0 +1,10 @@
|
||||
pluginManagement { |
||||
plugins { |
||||
id 'org.jetbrains.kotlin.jvm' version 'KOTLIN_VERSION_PLACEHOLDER' |
||||
id 'org.jetbrains.compose' version 'COMPOSE_VERSION_PLACEHOLDER' |
||||
} |
||||
repositories { |
||||
mavenLocal() |
||||
gradlePluginPortal() |
||||
} |
||||
} |
Loading…
Reference in new issue