Browse Source

Improve diagnosing problems with external tools

* Fix passing 'compose.desktop.verbose' to jlink/jpackage
* Save external tools logs to files in non-verbose mode
pull/395/head
Alexey Tsvetkov 4 years ago committed by Alexey Tsvetkov
parent
commit
9fcf0735eb
  1. 8
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractCheckNotarizationStatusTask.kt
  2. 108
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractComposeDesktopTask.kt
  3. 14
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractJPackageTask.kt
  4. 38
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractJvmToolOperationTask.kt
  5. 12
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractNotarizationTask.kt
  6. 7
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractRunDistributableTask.kt
  7. 35
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractUploadAppForNotarizationTask.kt

8
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractCheckNotarizationStatusTask.kt

@ -36,15 +36,15 @@ abstract class AbstractCheckNotarizationStatusTask : AbstractNotarizationTask()
for (request in requests.sortedBy { it.uploadTime }) { for (request in requests.sortedBy { it.uploadTime }) {
try { try {
logger.quiet("Checking status of notarization request '${request.uuid}'") logger.quiet("Checking status of notarization request '${request.uuid}'")
execOperations.exec { exec -> runExternalTool(
exec.executable = MacUtils.xcrun.absolutePath tool = MacUtils.xcrun,
exec.args( args = listOf(
"altool", "altool",
"--notarization-info", request.uuid, "--notarization-info", request.uuid,
"--username", notarization.appleID, "--username", notarization.appleID,
"--password", notarization.password "--password", notarization.password
) )
} )
} catch (e: Exception) { } catch (e: Exception) {
logger.error("Could not check notarization request '${request.uuid}'", e) logger.error("Could not check notarization request '${request.uuid}'", e)
} }

108
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractComposeDesktopTask.kt

@ -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"))
}

14
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractJPackageTask.kt

@ -7,7 +7,6 @@ import org.gradle.api.provider.Provider
import org.gradle.api.tasks.* import org.gradle.api.tasks.*
import org.gradle.api.tasks.Optional import org.gradle.api.tasks.Optional
import org.gradle.process.ExecResult import org.gradle.process.ExecResult
import org.gradle.process.ExecSpec
import org.gradle.work.ChangeType import org.gradle.work.ChangeType
import org.gradle.work.InputChanges import org.gradle.work.InputChanges
import org.jetbrains.compose.desktop.application.dsl.MacOSSigningSettings import org.jetbrains.compose.desktop.application.dsl.MacOSSigningSettings
@ -330,20 +329,17 @@ abstract class AbstractJPackageTask @Inject constructor(
} }
} }
override fun configureExec(exec: ExecSpec) { override fun jvmToolEnvironment(): MutableMap<String, String> =
super.configureExec(exec) super.jvmToolEnvironment().apply {
configureWixPathIfNeeded(exec)
}
private fun configureWixPathIfNeeded(exec: ExecSpec) {
if (currentOS == OS.Windows) { if (currentOS == OS.Windows) {
val wixDir = wixToolsetDir.ioFileOrNull ?: return val wixDir = wixToolsetDir.ioFile
val wixPath = wixDir.absolutePath val wixPath = wixDir.absolutePath
val path = System.getenv("PATH") ?: "" val path = System.getenv("PATH") ?: ""
exec.environment("PATH", "$wixPath;$path") put("PATH", "$wixPath;$path")
} }
} }
override fun checkResult(result: ExecResult) { override fun checkResult(result: ExecResult) {
super.checkResult(result) super.checkResult(result)
val outputFile = findOutputFileOrDir(destinationDir.ioFile, targetFormat) val outputFile = findOutputFileOrDir(destinationDir.ioFile, targetFormat)

38
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractJvmToolOperationTask.kt

@ -1,35 +1,21 @@
package org.jetbrains.compose.desktop.application.tasks package org.jetbrains.compose.desktop.application.tasks
import org.gradle.api.DefaultTask
import org.gradle.api.file.Directory import org.gradle.api.file.Directory
import org.gradle.api.file.DirectoryProperty 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.ListProperty
import org.gradle.api.provider.Property import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider import org.gradle.api.provider.Provider
import org.gradle.api.provider.ProviderFactory
import org.gradle.api.tasks.* import org.gradle.api.tasks.*
import org.gradle.process.ExecOperations
import org.gradle.process.ExecResult import org.gradle.process.ExecResult
import org.gradle.process.ExecSpec import org.gradle.process.ExecSpec
import org.gradle.work.InputChanges 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.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 org.jetbrains.compose.desktop.application.internal.notNullProperty
import java.io.File 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 @get:LocalState
protected val workingDir: Provider<Directory> = project.layout.buildDirectory.dir("compose/tmp/$name") 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")) 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) { protected open fun prepareWorkingDir(inputChanges: InputChanges) {
fileOperations.delete(workingDir) fileOperations.delete(workingDir)
fileOperations.mkdir(workingDir) fileOperations.mkdir(workingDir)
@ -59,7 +40,8 @@ abstract class AbstractJvmToolOperationTask(private val toolName: String) : Defa
freeArgs.orNull?.forEach { add(it) } 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) { protected open fun checkResult(result: ExecResult) {
result.assertNormalExitValue() result.assertNormalExitValue()
} }
@ -85,11 +67,11 @@ abstract class AbstractJvmToolOperationTask(private val toolName: String) : Defa
} }
try { try {
execOperations.exec { exec -> runExternalTool(
configureExec(exec) tool = jtool,
exec.executable = jtool.absolutePath args = listOf("@${argsFile.absolutePath}"),
exec.setArgs(listOf("@${argsFile.absolutePath}")) environment = jvmToolEnvironment()
}.also { checkResult(it) } ).also { checkResult(it) }
} finally { } finally {
if (!ComposeProperties.preserveWorkingDir(providers).get()) { if (!ComposeProperties.preserveWorkingDir(providers).get()) {
fileOperations.delete(workingDir) fileOperations.delete(workingDir)

12
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractNotarizationTask.kt

@ -1,24 +1,14 @@
package org.jetbrains.compose.desktop.application.tasks 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.provider.Property
import org.gradle.api.tasks.Input import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Nested import org.gradle.api.tasks.Nested
import org.gradle.api.tasks.Optional 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.dsl.MacOSNotarizationSettings
import org.jetbrains.compose.desktop.application.internal.nullableProperty import org.jetbrains.compose.desktop.application.internal.nullableProperty
import org.jetbrains.compose.desktop.application.internal.validation.validate 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:Input
@get:Optional @get:Optional
internal val nonValidatedBundleID: Property<String?> = objects.nullableProperty() internal val nonValidatedBundleID: Property<String?> = objects.nullableProperty()

7
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractRunDistributableTask.kt

@ -1,13 +1,11 @@
package org.jetbrains.compose.desktop.application.tasks package org.jetbrains.compose.desktop.application.tasks
import org.gradle.api.DefaultTask
import org.gradle.api.file.Directory import org.gradle.api.file.Directory
import org.gradle.api.provider.Provider import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Input import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.TaskAction import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.TaskProvider 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.OS
import org.jetbrains.compose.desktop.application.internal.currentOS import org.jetbrains.compose.desktop.application.internal.currentOS
import org.jetbrains.compose.desktop.application.internal.executableName import org.jetbrains.compose.desktop.application.internal.executableName
@ -18,9 +16,8 @@ import javax.inject.Inject
// lazy configuration yet. Lazy configuration is needed to // lazy configuration yet. Lazy configuration is needed to
// calculate appImageDir after the evaluation of createApplicationImage // calculate appImageDir after the evaluation of createApplicationImage
abstract class AbstractRunDistributableTask @Inject constructor( abstract class AbstractRunDistributableTask @Inject constructor(
createApplicationImage: TaskProvider<AbstractJPackageTask>, createApplicationImage: TaskProvider<AbstractJPackageTask>
private val execOperations: ExecOperations ) : AbstractComposeDesktopTask() {
) : DefaultTask() {
@get:InputDirectory @get:InputDirectory
internal val appImageRootDir: Provider<Directory> = createApplicationImage.flatMap { it.destinationDir } internal val appImageRootDir: Provider<Directory> = createApplicationImage.flatMap { it.destinationDir }

35
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractUploadAppForNotarizationTask.kt

@ -4,8 +4,7 @@ import org.gradle.api.file.DirectoryProperty
import org.gradle.api.tasks.* import org.gradle.api.tasks.*
import org.jetbrains.compose.desktop.application.dsl.TargetFormat import org.jetbrains.compose.desktop.application.dsl.TargetFormat
import org.jetbrains.compose.desktop.application.internal.* import org.jetbrains.compose.desktop.application.internal.*
import java.io.ByteArrayOutputStream import java.io.File
import java.io.PrintStream
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import javax.inject.Inject import javax.inject.Inject
@ -27,32 +26,26 @@ abstract class AbstractUploadAppForNotarizationTask @Inject constructor(
@TaskAction @TaskAction
fun run() { fun run() {
val notarization = validateNotarization() val notarization = validateNotarization()
val inputFile = findOutputFileOrDir(inputDir.ioFile, targetFormat) val packageFile = findOutputFileOrDir(inputDir.ioFile, targetFormat).checkExistingFile()
val file = inputFile.checkExistingFile()
logger.quiet("Uploading '${file.name}' for notarization (package id: '${notarization.bundleID}')") logger.quiet("Uploading '${packageFile.name}' for notarization (package id: '${notarization.bundleID}')")
val (res, output) = ByteArrayOutputStream().use { baos -> runExternalTool(
PrintStream(baos).use { ps -> tool = MacUtils.xcrun,
val res = execOperations.exec { exec -> args = listOf(
exec.executable = MacUtils.xcrun.absolutePath
exec.args(
"altool", "altool",
"--notarize-app", "--notarize-app",
"--primary-bundle-id", notarization.bundleID, "--primary-bundle-id", notarization.bundleID,
"--username", notarization.appleID, "--username", notarization.appleID,
"--password", notarization.password, "--password", notarization.password,
"--file", file "--file", packageFile.absolutePath
),
processStdout = { output ->
processUploadToolOutput(packageFile, output)
}
) )
exec.standardOutput = ps
} }
res to baos.toString() private fun processUploadToolOutput(packageFile: File, output: String) {
}
}
if (res.exitValue != 0) {
logger.error("Uploading failed. Stdout: $output")
res.assertNormalExitValue()
}
val m = "RequestUUID = ([A-Za-z0-9\\-]+)".toRegex().find(output) val m = "RequestUUID = ([A-Za-z0-9\\-]+)".toRegex().find(output)
?: error("Could not determine RequestUUID from output: $output") ?: error("Could not determine RequestUUID from output: $output")
@ -61,8 +54,8 @@ abstract class AbstractUploadAppForNotarizationTask @Inject constructor(
val uploadTime = LocalDateTime.now() val uploadTime = LocalDateTime.now()
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss")) .format(DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss"))
val requestDir = requestsDir.ioFile.resolve("$uploadTime-${targetFormat.id}") val requestDir = requestsDir.ioFile.resolve("$uploadTime-${targetFormat.id}")
val packageCopy = requestDir.resolve(inputFile.name) val packageCopy = requestDir.resolve(packageFile.name)
inputFile.copyTo(packageCopy) packageFile.copyTo(packageCopy)
val requestInfo = NotarizationRequestInfo(uuid = requestId, uploadTime = uploadTime) val requestInfo = NotarizationRequestInfo(uuid = requestId, uploadTime = uploadTime)
val requestInfoFile = requestDir.resolve(NOTARIZATION_REQUEST_INFO_FILE_NAME) val requestInfoFile = requestDir.resolve(NOTARIZATION_REQUEST_INFO_FILE_NAME)
requestInfo.saveTo(requestInfoFile) requestInfo.saveTo(requestInfoFile)

Loading…
Cancel
Save