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. 22
      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. 49
      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 }) {
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)
}

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

22
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.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)

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
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)

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
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()

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
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 }

49
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.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)

Loading…
Cancel
Save