Browse Source

Check notarization status without triggering new notarization upload

pull/387/head
Alexey Tsvetkov 4 years ago committed by Alexey Tsvetkov
parent
commit
f7feef4cc6
  1. 35
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/NotarizationRequestInfo.kt
  2. 16
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/configureApplication.kt
  3. 36
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractCheckNotarizationStatusTask.kt
  4. 26
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractUploadAppForNotarizationTask.kt
  5. 7
      tutorials/Signing_and_notarization_on_macOS/README.md

35
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/NotarizationRequestInfo.kt

@ -0,0 +1,35 @@
package org.jetbrains.compose.desktop.application.internal
import java.io.File
import java.util.*
internal const val NOTARIZATION_REQUEST_INFO_FILE_NAME = "notarization-request.properties"
internal data class NotarizationRequestInfo(
var uuid: String = "",
var uploadTime: String = ""
) {
fun loadFrom(file: File) {
val properties = Properties().apply {
file.inputStream().buffered().use { input ->
load(input)
}
}
uuid = properties.getProperty(UUID) ?: uuid
uploadTime = properties.getProperty(UPLOAD_TIME) ?: uploadTime
}
fun saveTo(file: File) {
val properties = Properties()
properties[UUID] = uuid
properties[UPLOAD_TIME] = uploadTime
file.outputStream().buffered().use { output ->
properties.store(output, null)
}
}
companion object {
private const val UUID = "uuid"
private const val UPLOAD_TIME = "upload.time"
}
}

16
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/configureApplication.kt

@ -1,6 +1,7 @@
package org.jetbrains.compose.desktop.application.internal package org.jetbrains.compose.desktop.application.internal
import org.gradle.api.* import org.gradle.api.*
import org.gradle.api.file.Directory
import org.gradle.api.file.DuplicatesStrategy import org.gradle.api.file.DuplicatesStrategy
import org.gradle.api.file.FileCollection import org.gradle.api.file.FileCollection
import org.gradle.api.plugins.JavaPluginConvention import org.gradle.api.plugins.JavaPluginConvention
@ -72,17 +73,18 @@ internal fun Project.configurePackagingTasks(apps: Collection<Application>) {
"Unexpected target format for MacOS: $targetFormat" "Unexpected target format for MacOS: $targetFormat"
} }
val notarizationRequestsDir = project.layout.buildDirectory.dir("compose/notarization/${app.name}")
val upload = tasks.composeTask<AbstractUploadAppForNotarizationTask>( val upload = tasks.composeTask<AbstractUploadAppForNotarizationTask>(
taskName("notarize", app, targetFormat.name), taskName("notarize", app, targetFormat.name),
args = listOf(targetFormat) args = listOf(targetFormat)
) { ) {
configureUploadForNotarizationTask(app, packageFormat, targetFormat) configureUploadForNotarizationTask(app, packageFormat, notarizationRequestsDir)
} }
tasks.composeTask<AbstractCheckNotarizationStatusTask>( tasks.composeTask<AbstractCheckNotarizationStatusTask>(
taskName("checkNotarizationStatus", app, targetFormat.name) taskName("checkNotarizationStatus", app)
) { ) {
configureCheckNotarizationStatusTask(app, upload) configureCheckNotarizationStatusTask(app, notarizationRequestsDir)
} }
} }
@ -163,19 +165,19 @@ internal fun AbstractJPackageTask.configurePackagingTask(
internal fun AbstractUploadAppForNotarizationTask.configureUploadForNotarizationTask( internal fun AbstractUploadAppForNotarizationTask.configureUploadForNotarizationTask(
app: Application, app: Application,
packageFormat: TaskProvider<AbstractJPackageTask>, packageFormat: TaskProvider<AbstractJPackageTask>,
targetFormat: TargetFormat requestsDir: Provider<Directory>
) { ) {
dependsOn(packageFormat) dependsOn(packageFormat)
inputDir.set(packageFormat.flatMap { it.destinationDir }) inputDir.set(packageFormat.flatMap { it.destinationDir })
requestIDFile.set(project.layout.buildDirectory.file("compose/notarization/${app.name}-${targetFormat.id}-request-id.txt")) this.requestsDir.set(requestsDir)
configureCommonNotarizationSettings(app) configureCommonNotarizationSettings(app)
} }
internal fun AbstractCheckNotarizationStatusTask.configureCheckNotarizationStatusTask( internal fun AbstractCheckNotarizationStatusTask.configureCheckNotarizationStatusTask(
app: Application, app: Application,
uploadTask: Provider<AbstractUploadAppForNotarizationTask> requestsDir: Provider<Directory>
) { ) {
requestIDFile.set(uploadTask.flatMap { it.requestIDFile }) requestDir.set(requestsDir)
configureCommonNotarizationSettings(app) configureCommonNotarizationSettings(app)
} }

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

@ -1,27 +1,53 @@
package org.jetbrains.compose.desktop.application.tasks package org.jetbrains.compose.desktop.application.tasks
import org.gradle.api.file.RegularFileProperty import org.gradle.api.file.DirectoryProperty
import org.gradle.api.tasks.* import org.gradle.api.tasks.*
import org.jetbrains.compose.desktop.application.internal.MacUtils import org.jetbrains.compose.desktop.application.internal.MacUtils
import org.jetbrains.compose.desktop.application.internal.NOTARIZATION_REQUEST_INFO_FILE_NAME
import org.jetbrains.compose.desktop.application.internal.NotarizationRequestInfo
import org.jetbrains.compose.desktop.application.internal.ioFile import org.jetbrains.compose.desktop.application.internal.ioFile
abstract class AbstractCheckNotarizationStatusTask : AbstractNotarizationTask() { abstract class AbstractCheckNotarizationStatusTask : AbstractNotarizationTask() {
@get:InputFile @get:Internal
val requestIDFile: RegularFileProperty = objects.fileProperty() val requestDir: DirectoryProperty = objects.directoryProperty()
@TaskAction @TaskAction
fun run() { fun run() {
val notarization = validateNotarization() val notarization = validateNotarization()
val requestId = requestIDFile.ioFile.readText() val requests = HashSet<NotarizationRequestInfo>()
for (file in requestDir.ioFile.walk()) {
if (file.isFile && file.name == NOTARIZATION_REQUEST_INFO_FILE_NAME) {
try {
val status = NotarizationRequestInfo()
status.loadFrom(file)
requests.add(status)
} catch (e: Exception) {
logger.error("Invalid notarization request status file: $file", e)
}
}
}
if (requests.isEmpty()) {
logger.quiet("No existing notarization requests")
return
}
for (request in requests.sortedBy { it.uploadTime }) {
try {
logger.quiet("Checking status of notarization request '${request.uuid}'")
execOperations.exec { exec -> execOperations.exec { exec ->
exec.executable = MacUtils.xcrun.absolutePath exec.executable = MacUtils.xcrun.absolutePath
exec.args( exec.args(
"altool", "altool",
"--notarization-info", requestId, "--notarization-info", request.uuid,
"--username", notarization.appleID, "--username", notarization.appleID,
"--password", notarization.password "--password", notarization.password
) )
} }
} catch (e: Exception) {
logger.error("Could not check notarization request '${request.uuid}'", e)
}
}
} }
} }

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

@ -1,23 +1,24 @@
package org.jetbrains.compose.desktop.application.tasks package org.jetbrains.compose.desktop.application.tasks
import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
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.ByteArrayOutputStream
import java.io.PrintStream import java.io.PrintStream
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import javax.inject.Inject import javax.inject.Inject
abstract class AbstractUploadAppForNotarizationTask @Inject constructor( abstract class AbstractUploadAppForNotarizationTask @Inject constructor(
@get:Input @get:Input
val targetFormat: TargetFormat, val targetFormat: TargetFormat
) : AbstractNotarizationTask() { ) : AbstractNotarizationTask() {
@get:InputDirectory @get:InputDirectory
val inputDir: DirectoryProperty = objects.directoryProperty() val inputDir: DirectoryProperty = objects.directoryProperty()
@get:OutputFile @get:Internal
val requestIDFile: RegularFileProperty = objects.fileProperty() val requestsDir: DirectoryProperty = objects.directoryProperty()
init { init {
check(targetFormat != TargetFormat.AppImage) { "${TargetFormat.AppImage} cannot be notarized!" } check(targetFormat != TargetFormat.AppImage) { "${TargetFormat.AppImage} cannot be notarized!" }
@ -26,7 +27,6 @@ abstract class AbstractUploadAppForNotarizationTask @Inject constructor(
@TaskAction @TaskAction
fun run() { fun run() {
val notarization = validateNotarization() val notarization = validateNotarization()
val inputFile = findOutputFileOrDir(inputDir.ioFile, targetFormat) val inputFile = findOutputFileOrDir(inputDir.ioFile, targetFormat)
val file = inputFile.checkExistingFile() val file = inputFile.checkExistingFile()
@ -57,12 +57,18 @@ abstract class AbstractUploadAppForNotarizationTask @Inject constructor(
?: error("Could not determine RequestUUID from output: $output") ?: error("Could not determine RequestUUID from output: $output")
val requestId = m.groupValues[1] val requestId = m.groupValues[1]
requestIDFile.ioFile.apply {
parentFile.mkdirs() val uploadTime = LocalDateTime.now()
writeText(requestId) .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 requestInfo = NotarizationRequestInfo(uuid = requestId, uploadTime = uploadTime)
val requestInfoFile = requestDir.resolve(NOTARIZATION_REQUEST_INFO_FILE_NAME)
requestInfo.saveTo(requestInfoFile)
logger.quiet("Request UUID: $requestId") logger.quiet("Request UUID: $requestId")
logger.quiet("Request UUID is saved to ${requestIDFile.ioFile.absolutePath}") logger.quiet("Request UUID is saved to ${requestInfoFile.absolutePath}")
logger.quiet("Uploaded file is saved to ${packageCopy.absolutePath}")
} }
} }

7
tutorials/Signing_and_notarization_on_macOS/README.md

@ -215,12 +215,13 @@ macOS {
The following tasks are available: The following tasks are available:
* Use `createDistributable` or `packageDmg` to get a signed application * Use `createDistributable` or `packageDmg` to get a signed application
(no separate step is required). (no separate step is required).
* Use `notarizeDmg` to upload an application for notarization. * Use `notarize<PACKAGING_FORMAT>` (e.g. `notarizeDmg`) to upload an application for notarization.
Once the upload finishes, a `RequestUUID` will be printed. Once the upload finishes, a `RequestUUID` will be printed.
The notarization process takes some time. The notarization process takes some time.
Once the notarization process finishes, an email will be sent to you. Once the notarization process finishes, an email will be sent to you.
* Use `checkNotarizationStatus<PACKAGING_FORMAT>` to check a status of Uploaded file is saved to `<BUILD_DIR>/compose/notarization/main/<UPLOAD_DATE>-<PACKAGING_FORMAT>`
the last notarization request. You can also use a command-line command directly: * Use `checkNotarizationStatus` to check a status of
last notarization requests. You can also use a command-line command to check any notarization request:
``` ```
xcrun altool --notarization-info <RequestUUID> xcrun altool --notarization-info <RequestUUID>
--username <Apple_ID> --username <Apple_ID>

Loading…
Cancel
Save