diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/NotarizationRequestInfo.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/NotarizationRequestInfo.kt new file mode 100644 index 0000000000..fabccf9d25 --- /dev/null +++ b/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" + } +} \ No newline at end of file diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/configureApplication.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/configureApplication.kt index 108074317a..26a8579d4e 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/configureApplication.kt +++ b/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 import org.gradle.api.* +import org.gradle.api.file.Directory import org.gradle.api.file.DuplicatesStrategy import org.gradle.api.file.FileCollection import org.gradle.api.plugins.JavaPluginConvention @@ -72,17 +73,18 @@ internal fun Project.configurePackagingTasks(apps: Collection) { "Unexpected target format for MacOS: $targetFormat" } + val notarizationRequestsDir = project.layout.buildDirectory.dir("compose/notarization/${app.name}") val upload = tasks.composeTask( taskName("notarize", app, targetFormat.name), args = listOf(targetFormat) ) { - configureUploadForNotarizationTask(app, packageFormat, targetFormat) + configureUploadForNotarizationTask(app, packageFormat, notarizationRequestsDir) } tasks.composeTask( - 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( app: Application, packageFormat: TaskProvider, - targetFormat: TargetFormat + requestsDir: Provider ) { dependsOn(packageFormat) 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) } internal fun AbstractCheckNotarizationStatusTask.configureCheckNotarizationStatusTask( app: Application, - uploadTask: Provider + requestsDir: Provider ) { - requestIDFile.set(uploadTask.flatMap { it.requestIDFile }) + requestDir.set(requestsDir) configureCommonNotarizationSettings(app) } diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractCheckNotarizationStatusTask.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractCheckNotarizationStatusTask.kt index 677119aea0..faedf66024 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractCheckNotarizationStatusTask.kt +++ b/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 -import org.gradle.api.file.RegularFileProperty +import org.gradle.api.file.DirectoryProperty import org.gradle.api.tasks.* 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 abstract class AbstractCheckNotarizationStatusTask : AbstractNotarizationTask() { - @get:InputFile - val requestIDFile: RegularFileProperty = objects.fileProperty() + @get:Internal + val requestDir: DirectoryProperty = objects.directoryProperty() @TaskAction fun run() { val notarization = validateNotarization() - val requestId = requestIDFile.ioFile.readText() - execOperations.exec { exec -> - exec.executable = MacUtils.xcrun.absolutePath - exec.args( - "altool", - "--notarization-info", requestId, - "--username", notarization.appleID, - "--password", notarization.password - ) + val requests = HashSet() + 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 -> + exec.executable = MacUtils.xcrun.absolutePath + exec.args( + "altool", + "--notarization-info", request.uuid, + "--username", notarization.appleID, + "--password", notarization.password + ) + } + } catch (e: Exception) { + logger.error("Could not check notarization request '${request.uuid}'", e) + } } } } \ No newline at end of file diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractUploadAppForNotarizationTask.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractUploadAppForNotarizationTask.kt index f962b5899b..e46694bb94 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractUploadAppForNotarizationTask.kt +++ b/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 import org.gradle.api.file.DirectoryProperty -import org.gradle.api.file.RegularFileProperty 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.time.LocalDateTime +import java.time.format.DateTimeFormatter import javax.inject.Inject abstract class AbstractUploadAppForNotarizationTask @Inject constructor( @get:Input - val targetFormat: TargetFormat, + val targetFormat: TargetFormat ) : AbstractNotarizationTask() { @get:InputDirectory val inputDir: DirectoryProperty = objects.directoryProperty() - @get:OutputFile - val requestIDFile: RegularFileProperty = objects.fileProperty() + @get:Internal + val requestsDir: DirectoryProperty = objects.directoryProperty() init { check(targetFormat != TargetFormat.AppImage) { "${TargetFormat.AppImage} cannot be notarized!" } @@ -26,7 +27,6 @@ abstract class AbstractUploadAppForNotarizationTask @Inject constructor( @TaskAction fun run() { val notarization = validateNotarization() - val inputFile = findOutputFileOrDir(inputDir.ioFile, targetFormat) val file = inputFile.checkExistingFile() @@ -57,12 +57,18 @@ abstract class AbstractUploadAppForNotarizationTask @Inject constructor( ?: error("Could not determine RequestUUID from output: $output") val requestId = m.groupValues[1] - requestIDFile.ioFile.apply { - parentFile.mkdirs() - writeText(requestId) - } + + 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 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 is saved to ${requestIDFile.ioFile.absolutePath}") + logger.quiet("Request UUID is saved to ${requestInfoFile.absolutePath}") + logger.quiet("Uploaded file is saved to ${packageCopy.absolutePath}") } } diff --git a/tutorials/Signing_and_notarization_on_macOS/README.md b/tutorials/Signing_and_notarization_on_macOS/README.md index e0f409e503..6af2433adf 100644 --- a/tutorials/Signing_and_notarization_on_macOS/README.md +++ b/tutorials/Signing_and_notarization_on_macOS/README.md @@ -215,12 +215,13 @@ macOS { The following tasks are available: * Use `createDistributable` or `packageDmg` to get a signed application (no separate step is required). -* Use `notarizeDmg` to upload an application for notarization. +* Use `notarize` (e.g. `notarizeDmg`) to upload an application for notarization. Once the upload finishes, a `RequestUUID` will be printed. The notarization process takes some time. Once the notarization process finishes, an email will be sent to you. -* Use `checkNotarizationStatus` to check a status of - the last notarization request. You can also use a command-line command directly: + Uploaded file is saved to `/compose/notarization/main/-` +* 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 --username