mirror of
https://github.com/jlengrand/compose-multiplatform.git
synced 2026-03-10 08:11:20 +00:00
Improve diagnosing problems with external tools
* Fix passing 'compose.desktop.verbose' to jlink/jpackage * Save external tools logs to files in non-verbose mode
This commit is contained in:
committed by
Alexey Tsvetkov
parent
f7feef4cc6
commit
9fcf0735eb
@@ -36,15 +36,15 @@ abstract class AbstractCheckNotarizationStatusTask : AbstractNotarizationTask()
|
||||
for (request in requests.sortedBy { it.uploadTime }) {
|
||||
try {
|
||||
logger.quiet("Checking status of notarization request '${request.uuid}'")
|
||||
execOperations.exec { exec ->
|
||||
exec.executable = MacUtils.xcrun.absolutePath
|
||||
exec.args(
|
||||
runExternalTool(
|
||||
tool = MacUtils.xcrun,
|
||||
args = listOf(
|
||||
"altool",
|
||||
"--notarization-info", request.uuid,
|
||||
"--username", notarization.appleID,
|
||||
"--password", notarization.password
|
||||
)
|
||||
}
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
logger.error("Could not check notarization request '${request.uuid}'", e)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
package org.jetbrains.compose.desktop.application.tasks
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.Directory
|
||||
import org.gradle.api.internal.file.FileOperations
|
||||
import org.gradle.api.model.ObjectFactory
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.provider.Provider
|
||||
import org.gradle.api.provider.ProviderFactory
|
||||
import org.gradle.api.tasks.Internal
|
||||
import org.gradle.api.tasks.LocalState
|
||||
import org.gradle.process.ExecOperations
|
||||
import org.gradle.process.ExecResult
|
||||
import org.jetbrains.compose.desktop.application.internal.ComposeProperties
|
||||
import org.jetbrains.compose.desktop.application.internal.ioFile
|
||||
import org.jetbrains.compose.desktop.application.internal.notNullProperty
|
||||
import java.io.File
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.function.Consumer
|
||||
import javax.inject.Inject
|
||||
|
||||
abstract class AbstractComposeDesktopTask : DefaultTask() {
|
||||
@get:Inject
|
||||
protected abstract val objects: ObjectFactory
|
||||
|
||||
@get:Inject
|
||||
protected abstract val providers: ProviderFactory
|
||||
|
||||
@get:Inject
|
||||
protected abstract val execOperations: ExecOperations
|
||||
|
||||
@get:Inject
|
||||
protected abstract val fileOperations: FileOperations
|
||||
|
||||
@get:LocalState
|
||||
protected val logsDir: Provider<Directory> = project.layout.buildDirectory.dir("compose/logs/$name")
|
||||
|
||||
@get:Internal
|
||||
val verbose: Property<Boolean> = objects.notNullProperty<Boolean>().apply {
|
||||
set(providers.provider {
|
||||
logger.isDebugEnabled || ComposeProperties.isVerbose(providers).get()
|
||||
})
|
||||
}
|
||||
|
||||
internal fun runExternalTool(
|
||||
tool: File,
|
||||
args: Collection<String>,
|
||||
environment: Map<String, Any> = emptyMap(),
|
||||
workingDir: File? = null,
|
||||
checkExitCodeIsNormal: Boolean = true,
|
||||
processStdout: Function1<String, Unit>? = null
|
||||
): ExecResult {
|
||||
val logsDir = logsDir.ioFile
|
||||
logsDir.mkdirs()
|
||||
|
||||
val toolName = tool.nameWithoutExtension
|
||||
val outFile = logsDir.resolve("${toolName}-${currentTimeStamp()}-out.txt")
|
||||
val errFile = logsDir.resolve("${toolName}-${currentTimeStamp()}-out.txt")
|
||||
|
||||
val result = outFile.outputStream().buffered().use { outStream ->
|
||||
errFile.outputStream().buffered().use { errStream ->
|
||||
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 (!verbose.get()) {
|
||||
spec.standardOutput = outStream
|
||||
spec.errorOutput = errStream
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (checkExitCodeIsNormal && result.exitValue != 0) {
|
||||
val errMsg = buildString {
|
||||
appendln("External tool execution failed:")
|
||||
val cmd = (listOf(tool.absolutePath) + args).joinToString(", ")
|
||||
appendln("* Command: [$cmd]")
|
||||
appendln("* Working dir: [${workingDir?.absolutePath.orEmpty()}]")
|
||||
appendln("* Exit code: ${result.exitValue}")
|
||||
appendln("* Standard output log: ${outFile.absolutePath}")
|
||||
appendln("* 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"))
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import org.gradle.api.provider.Provider
|
||||
import org.gradle.api.tasks.*
|
||||
import org.gradle.api.tasks.Optional
|
||||
import org.gradle.process.ExecResult
|
||||
import org.gradle.process.ExecSpec
|
||||
import org.gradle.work.ChangeType
|
||||
import org.gradle.work.InputChanges
|
||||
import org.jetbrains.compose.desktop.application.dsl.MacOSSigningSettings
|
||||
@@ -330,19 +329,16 @@ abstract class AbstractJPackageTask @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override fun configureExec(exec: ExecSpec) {
|
||||
super.configureExec(exec)
|
||||
configureWixPathIfNeeded(exec)
|
||||
}
|
||||
|
||||
private fun configureWixPathIfNeeded(exec: ExecSpec) {
|
||||
if (currentOS == OS.Windows) {
|
||||
val wixDir = wixToolsetDir.ioFileOrNull ?: return
|
||||
val wixPath = wixDir.absolutePath
|
||||
val path = System.getenv("PATH") ?: ""
|
||||
exec.environment("PATH", "$wixPath;$path")
|
||||
override fun jvmToolEnvironment(): MutableMap<String, String> =
|
||||
super.jvmToolEnvironment().apply {
|
||||
if (currentOS == OS.Windows) {
|
||||
val wixDir = wixToolsetDir.ioFile
|
||||
val wixPath = wixDir.absolutePath
|
||||
val path = System.getenv("PATH") ?: ""
|
||||
put("PATH", "$wixPath;$path")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun checkResult(result: ExecResult) {
|
||||
super.checkResult(result)
|
||||
|
||||
@@ -1,35 +1,21 @@
|
||||
package org.jetbrains.compose.desktop.application.tasks
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.Directory
|
||||
import org.gradle.api.file.DirectoryProperty
|
||||
import org.gradle.api.internal.file.FileOperations
|
||||
import org.gradle.api.model.ObjectFactory
|
||||
import org.gradle.api.provider.ListProperty
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.provider.Provider
|
||||
import org.gradle.api.provider.ProviderFactory
|
||||
import org.gradle.api.tasks.*
|
||||
import org.gradle.process.ExecOperations
|
||||
import org.gradle.process.ExecResult
|
||||
import org.gradle.process.ExecSpec
|
||||
import org.gradle.work.InputChanges
|
||||
import org.jetbrains.compose.desktop.application.internal.*
|
||||
import org.jetbrains.compose.desktop.application.internal.ComposeProperties
|
||||
import org.jetbrains.compose.desktop.application.internal.executableName
|
||||
import org.jetbrains.compose.desktop.application.internal.ioFile
|
||||
import org.jetbrains.compose.desktop.application.internal.notNullProperty
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
|
||||
abstract class AbstractJvmToolOperationTask(private val toolName: String) : DefaultTask() {
|
||||
@get:Inject
|
||||
protected abstract val objects: ObjectFactory
|
||||
@get:Inject
|
||||
protected abstract val providers: ProviderFactory
|
||||
@get:Inject
|
||||
protected abstract val execOperations: ExecOperations
|
||||
@get:Inject
|
||||
protected abstract val fileOperations: FileOperations
|
||||
|
||||
abstract class AbstractJvmToolOperationTask(private val toolName: String) : AbstractComposeDesktopTask() {
|
||||
@get:LocalState
|
||||
protected val workingDir: Provider<Directory> = project.layout.buildDirectory.dir("compose/tmp/$name")
|
||||
|
||||
@@ -45,11 +31,6 @@ abstract class AbstractJvmToolOperationTask(private val toolName: String) : Defa
|
||||
set(providers.systemProperty("java.home"))
|
||||
}
|
||||
|
||||
@get:Internal
|
||||
val verbose: Property<Boolean> = objects.notNullProperty<Boolean>().apply {
|
||||
set(providers.provider { logger.isDebugEnabled }.orElse(ComposeProperties.isVerbose(providers)))
|
||||
}
|
||||
|
||||
protected open fun prepareWorkingDir(inputChanges: InputChanges) {
|
||||
fileOperations.delete(workingDir)
|
||||
fileOperations.mkdir(workingDir)
|
||||
@@ -59,7 +40,8 @@ abstract class AbstractJvmToolOperationTask(private val toolName: String) : Defa
|
||||
freeArgs.orNull?.forEach { add(it) }
|
||||
}
|
||||
|
||||
protected open fun configureExec(exec: ExecSpec) {}
|
||||
protected open fun jvmToolEnvironment(): MutableMap<String, String> =
|
||||
HashMap()
|
||||
protected open fun checkResult(result: ExecResult) {
|
||||
result.assertNormalExitValue()
|
||||
}
|
||||
@@ -85,11 +67,11 @@ abstract class AbstractJvmToolOperationTask(private val toolName: String) : Defa
|
||||
}
|
||||
|
||||
try {
|
||||
execOperations.exec { exec ->
|
||||
configureExec(exec)
|
||||
exec.executable = jtool.absolutePath
|
||||
exec.setArgs(listOf("@${argsFile.absolutePath}"))
|
||||
}.also { checkResult(it) }
|
||||
runExternalTool(
|
||||
tool = jtool,
|
||||
args = listOf("@${argsFile.absolutePath}"),
|
||||
environment = jvmToolEnvironment()
|
||||
).also { checkResult(it) }
|
||||
} finally {
|
||||
if (!ComposeProperties.preserveWorkingDir(providers).get()) {
|
||||
fileOperations.delete(workingDir)
|
||||
|
||||
@@ -1,24 +1,14 @@
|
||||
package org.jetbrains.compose.desktop.application.tasks
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.model.ObjectFactory
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.Nested
|
||||
import org.gradle.api.tasks.Optional
|
||||
import org.gradle.process.ExecOperations
|
||||
import org.jetbrains.compose.desktop.application.dsl.MacOSNotarizationSettings
|
||||
import org.jetbrains.compose.desktop.application.internal.nullableProperty
|
||||
import org.jetbrains.compose.desktop.application.internal.validation.validate
|
||||
import javax.inject.Inject
|
||||
|
||||
abstract class AbstractNotarizationTask(
|
||||
) : DefaultTask() {
|
||||
@get:Inject
|
||||
protected abstract val objects: ObjectFactory
|
||||
@get:Inject
|
||||
protected abstract val execOperations: ExecOperations
|
||||
|
||||
abstract class AbstractNotarizationTask : AbstractComposeDesktopTask() {
|
||||
@get:Input
|
||||
@get:Optional
|
||||
internal val nonValidatedBundleID: Property<String?> = objects.nullableProperty()
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
package org.jetbrains.compose.desktop.application.tasks
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.Directory
|
||||
import org.gradle.api.provider.Provider
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.InputDirectory
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.gradle.api.tasks.TaskProvider
|
||||
import org.gradle.process.ExecOperations
|
||||
import org.jetbrains.compose.desktop.application.internal.OS
|
||||
import org.jetbrains.compose.desktop.application.internal.currentOS
|
||||
import org.jetbrains.compose.desktop.application.internal.executableName
|
||||
@@ -18,9 +16,8 @@ import javax.inject.Inject
|
||||
// lazy configuration yet. Lazy configuration is needed to
|
||||
// calculate appImageDir after the evaluation of createApplicationImage
|
||||
abstract class AbstractRunDistributableTask @Inject constructor(
|
||||
createApplicationImage: TaskProvider<AbstractJPackageTask>,
|
||||
private val execOperations: ExecOperations
|
||||
) : DefaultTask() {
|
||||
createApplicationImage: TaskProvider<AbstractJPackageTask>
|
||||
) : AbstractComposeDesktopTask() {
|
||||
@get:InputDirectory
|
||||
internal val appImageRootDir: Provider<Directory> = createApplicationImage.flatMap { it.destinationDir }
|
||||
|
||||
|
||||
@@ -4,8 +4,7 @@ import org.gradle.api.file.DirectoryProperty
|
||||
import org.gradle.api.tasks.*
|
||||
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
|
||||
import org.jetbrains.compose.desktop.application.internal.*
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.PrintStream
|
||||
import java.io.File
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import javax.inject.Inject
|
||||
@@ -27,32 +26,26 @@ abstract class AbstractUploadAppForNotarizationTask @Inject constructor(
|
||||
@TaskAction
|
||||
fun run() {
|
||||
val notarization = validateNotarization()
|
||||
val inputFile = findOutputFileOrDir(inputDir.ioFile, targetFormat)
|
||||
val file = inputFile.checkExistingFile()
|
||||
val packageFile = findOutputFileOrDir(inputDir.ioFile, targetFormat).checkExistingFile()
|
||||
|
||||
logger.quiet("Uploading '${file.name}' for notarization (package id: '${notarization.bundleID}')")
|
||||
val (res, output) = ByteArrayOutputStream().use { baos ->
|
||||
PrintStream(baos).use { ps ->
|
||||
val res = execOperations.exec { exec ->
|
||||
exec.executable = MacUtils.xcrun.absolutePath
|
||||
exec.args(
|
||||
"altool",
|
||||
"--notarize-app",
|
||||
"--primary-bundle-id", notarization.bundleID,
|
||||
"--username", notarization.appleID,
|
||||
"--password", notarization.password,
|
||||
"--file", file
|
||||
)
|
||||
exec.standardOutput = ps
|
||||
}
|
||||
|
||||
res to baos.toString()
|
||||
logger.quiet("Uploading '${packageFile.name}' for notarization (package id: '${notarization.bundleID}')")
|
||||
runExternalTool(
|
||||
tool = MacUtils.xcrun,
|
||||
args = listOf(
|
||||
"altool",
|
||||
"--notarize-app",
|
||||
"--primary-bundle-id", notarization.bundleID,
|
||||
"--username", notarization.appleID,
|
||||
"--password", notarization.password,
|
||||
"--file", packageFile.absolutePath
|
||||
),
|
||||
processStdout = { output ->
|
||||
processUploadToolOutput(packageFile, output)
|
||||
}
|
||||
}
|
||||
if (res.exitValue != 0) {
|
||||
logger.error("Uploading failed. Stdout: $output")
|
||||
res.assertNormalExitValue()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun processUploadToolOutput(packageFile: File, output: String) {
|
||||
val m = "RequestUUID = ([A-Za-z0-9\\-]+)".toRegex().find(output)
|
||||
?: error("Could not determine RequestUUID from output: $output")
|
||||
|
||||
@@ -61,8 +54,8 @@ abstract class AbstractUploadAppForNotarizationTask @Inject constructor(
|
||||
val uploadTime = LocalDateTime.now()
|
||||
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss"))
|
||||
val requestDir = requestsDir.ioFile.resolve("$uploadTime-${targetFormat.id}")
|
||||
val packageCopy = requestDir.resolve(inputFile.name)
|
||||
inputFile.copyTo(packageCopy)
|
||||
val packageCopy = requestDir.resolve(packageFile.name)
|
||||
packageFile.copyTo(packageCopy)
|
||||
val requestInfo = NotarizationRequestInfo(uuid = requestId, uploadTime = uploadTime)
|
||||
val requestInfoFile = requestDir.resolve(NOTARIZATION_REQUEST_INFO_FILE_NAME)
|
||||
requestInfo.saveTo(requestInfoFile)
|
||||
|
||||
Reference in New Issue
Block a user