From d1908bb41a8aa7023d06a3ca48336585b20a872d Mon Sep 17 00:00:00 2001 From: Alexey Tsvetkov Date: Tue, 20 Apr 2021 15:44:49 +0300 Subject: [PATCH] Re-sign app after patching Info.plist Resolves #602 --- .../internal/ExternalToolRunner.kt | 89 ++++++++++++++++++ .../desktop/application/internal/MacSigner.kt | 73 ++++++++++++++ .../files/MacJarSignFileCopyingProcessor.kt | 67 +------------ .../validation/validatePackageVersions.kt | 6 +- .../application/tasks/AbstractJPackageTask.kt | 37 ++++---- .../tasks/AbstractComposeDesktopTask.kt | 72 +------------- .../compose/gradle/DesktopApplicationTest.kt | 54 ++++++++++- .../jetbrains/compose/test/TestProjects.kt | 1 + .../org/jetbrains/compose/test/fileUtils.kt | 6 +- .../jetbrains/compose/test/processUtils.kt | 49 ++++++++++ .../application/macSign/build.gradle | 37 ++++++++ .../application/macSign/compose.test.keychain | Bin 0 -> 25820 bytes .../application/macSign/settings.gradle | 10 ++ .../macSign/src/main/kotlin/main.kt | 2 + 14 files changed, 344 insertions(+), 159 deletions(-) create mode 100644 gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/ExternalToolRunner.kt create mode 100644 gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/MacSigner.kt create mode 100644 gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/processUtils.kt create mode 100644 gradle-plugins/compose/src/test/test-projects/application/macSign/build.gradle create mode 100644 gradle-plugins/compose/src/test/test-projects/application/macSign/compose.test.keychain create mode 100644 gradle-plugins/compose/src/test/test-projects/application/macSign/settings.gradle create mode 100644 gradle-plugins/compose/src/test/test-projects/application/macSign/src/main/kotlin/main.kt diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/ExternalToolRunner.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/ExternalToolRunner.kt new file mode 100644 index 0000000000..7f3e6a998f --- /dev/null +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/ExternalToolRunner.kt @@ -0,0 +1,89 @@ +/* + * Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. + */ + +package org.jetbrains.compose.desktop.application.internal + +import org.gradle.api.file.Directory +import org.gradle.api.provider.Property +import org.gradle.api.provider.Provider +import org.gradle.process.ExecOperations +import org.gradle.process.ExecResult +import java.io.File +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter + +internal class ExternalToolRunner( + private val verbose: Property, + private val logsDir: Provider, + private val execOperations: ExecOperations +) { + operator fun invoke( + tool: File, + args: Collection, + environment: Map = emptyMap(), + workingDir: File? = null, + checkExitCodeIsNormal: Boolean = true, + processStdout: Function1? = null, + forceLogToFile: Boolean = false + ): ExecResult { + val logsDir = logsDir.ioFile + logsDir.mkdirs() + + val toolName = tool.nameWithoutExtension + val logToConsole = verbose.get() && !forceLogToFile + val outFile = logsDir.resolve("${toolName}-${currentTimeStamp()}-out.txt") + val errFile = logsDir.resolve("${toolName}-${currentTimeStamp()}-err.txt") + + val result = outFile.outputStream().buffered().use { outFileStream -> + errFile.outputStream().buffered().use { errFileStream -> + 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 (logToConsole) { + spec.standardOutput = spec.standardOutput.alsoOutputTo(outFileStream) + spec.errorOutput = spec.errorOutput.alsoOutputTo(errFileStream) + } else { + spec.standardOutput = outFileStream + spec.errorOutput = errFileStream + } + } + } + } + + if (checkExitCodeIsNormal && result.exitValue != 0) { + val errMsg = buildString { + appendLine("External tool execution failed:") + val cmd = (listOf(tool.absolutePath) + args).joinToString(", ") + appendLine("* Command: [$cmd]") + appendLine("* Working dir: [${workingDir?.absolutePath.orEmpty()}]") + appendLine("* Exit code: ${result.exitValue}") + appendLine("* Standard output log: ${outFile.absolutePath}") + appendLine("* 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")) +} \ No newline at end of file diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/MacSigner.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/MacSigner.kt new file mode 100644 index 0000000000..3eb6e80cab --- /dev/null +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/MacSigner.kt @@ -0,0 +1,73 @@ +/* + * Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. + */ + +package org.jetbrains.compose.desktop.application.internal + +import org.jetbrains.compose.desktop.application.internal.validation.ValidatedMacOSSigningSettings +import java.io.File +import java.util.regex.Pattern + +internal class MacSigner( + val settings: ValidatedMacOSSigningSettings, + private val runExternalTool: ExternalToolRunner +) { + private lateinit var signKey: String + + init { + runExternalTool( + MacUtils.security, + args = listOfNotNull( + "find-certificate", + "-a", + "-c", + settings.fullDeveloperID, + settings.keychain?.absolutePath + ), + processStdout = { stdout -> + signKey = findCertificate(stdout) + } + ) + } + + fun sign(file: File) { + val args = arrayListOf( + "-vvvv", + "--timestamp", + "--options", "runtime", + "--force", + "--prefix", settings.prefix, + "--sign", signKey + ) + + settings.keychain?.let { + args.add("--keychain") + args.add(it.absolutePath) + } + + args.add(file.absolutePath) + + runExternalTool(MacUtils.codesign, args) + } + + private fun findCertificate(certificates: String): String { + val regex = Pattern.compile("\"alis\"=\"([^\"]+)\"") + val m = regex.matcher(certificates) + if (!m.find()) { + val keychainPath = settings.keychain?.absolutePath + error( + "Could not find certificate for '${settings.identity}'" + + " in keychain [${keychainPath.orEmpty()}]" + ) + } + + val result = m.group(1) + if (m.find()) + error( + "Multiple matching certificates are found for '${settings.fullDeveloperID}'. " + + "Please specify keychain containing unique matching certificate." + ) + return result + } +} \ No newline at end of file diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/files/MacJarSignFileCopyingProcessor.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/files/MacJarSignFileCopyingProcessor.kt index 9489b34985..52d87052c8 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/files/MacJarSignFileCopyingProcessor.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/files/MacJarSignFileCopyingProcessor.kt @@ -5,60 +5,24 @@ package org.jetbrains.compose.desktop.application.internal.files -import org.gradle.process.ExecOperations -import org.jetbrains.compose.desktop.application.internal.MacUtils +import org.jetbrains.compose.desktop.application.internal.MacSigner import org.jetbrains.compose.desktop.application.internal.isJarFile -import org.jetbrains.compose.desktop.application.internal.validation.ValidatedMacOSSigningSettings -import java.io.ByteArrayOutputStream import java.io.File -import java.io.PrintStream -import java.util.regex.Pattern import java.util.zip.ZipEntry import java.util.zip.ZipInputStream import java.util.zip.ZipOutputStream internal class MacJarSignFileCopyingProcessor( + private val signer: MacSigner, private val tempDir: File, - private val execOperations: ExecOperations, - private val signing: ValidatedMacOSSigningSettings ) : FileCopyingProcessor { - private val signKey: String - - init { - val certificates = ByteArrayOutputStream().use { baos -> - PrintStream(baos).use { ps -> - execOperations.exec { exec -> - exec.executable = MacUtils.security.absolutePath - val args = arrayListOf("find-certificate", "-a", "-c", signing.fullDeveloperID) - signing.keychain?.let { args.add(it.absolutePath) } - exec.args(*args.toTypedArray()) - exec.standardOutput = ps - } - } - baos.toString() - } - val regex = Pattern.compile("\"alis\"=\"([^\"]+)\"") - val m = regex.matcher(certificates) - if (!m.find()) { - val keychainPath = signing.keychain?.absolutePath - error( - "Could not find certificate for '${signing.identity}'" + - " in keychain [${keychainPath.orEmpty()}]" - ) - } - - signKey = m.group(1) - if (m.find()) error("Multiple matching certificates are found for '${signing.fullDeveloperID}'. " + - "Please specify keychain containing unique matching certificate.") - } - override fun copy(source: File, target: File) { if (source.isJarFile) { signNativeLibsInJar(source, target) } else { SimpleFileCopyingProcessor.copy(source, target) if (source.name.isDylibPath) { - signDylib(target) + signer.sign(target) } } } @@ -81,7 +45,7 @@ internal class MacJarSignFileCopyingProcessor( val unpackedDylibFile = tempDir.resolve(sourceEntry.name.substringAfterLast("/")) try { zin.copyTo(unpackedDylibFile) - signDylib(unpackedDylibFile) + signer.sign(unpackedDylibFile) val targetEntry = ZipEntry(sourceEntry.name).apply { comment = sourceEntry.comment extra = sourceEntry.extra @@ -95,29 +59,6 @@ internal class MacJarSignFileCopyingProcessor( unpackedDylibFile.delete() } } - - private fun signDylib(dylibFile: File) { - val args = arrayListOf( - "-vvvv", - "--timestamp", - "--options", "runtime", - "--force", - "--prefix", signing.prefix, - "--sign", signKey - ) - - signing.keychain?.let { - args.add("--keychain") - args.add(it.absolutePath) - } - - args.add(dylibFile.absolutePath) - - execOperations.exec { exec -> - exec.executable = MacUtils.codesign.absolutePath - exec.args(*args.toTypedArray()) - }.assertNormalExitValue() - } } private val String.isDylibPath diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/validation/validatePackageVersions.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/validation/validatePackageVersions.kt index 61405596b4..5fa1ef79db 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/validation/validatePackageVersions.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/validation/validatePackageVersions.kt @@ -58,11 +58,11 @@ private class ErrorsCollector { correctFormat: String? = null ) { val msg = buildString { - appendln("* Illegal version for '$targetFormat': $error.") + appendLine("* Illegal version for '$targetFormat': $error.") if (correctFormat != null) { - appendln(" * Correct format: $correctFormat") + appendLine(" * Correct format: $correctFormat") } - appendln(" * You can specify the correct version using DSL properties: " + + appendLine(" * You can specify the correct version using DSL properties: " + dslPropertiesFor(targetFormat).joinToString(", ") ) } diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractJPackageTask.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractJPackageTask.kt index e508982a06..728f6c4059 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractJPackageTask.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractJPackageTask.kt @@ -172,12 +172,13 @@ abstract class AbstractJPackageTask @Inject constructor( @get:Nested internal var nonValidatedMacSigningSettings: MacOSSigningSettings? = null - private inline fun withValidatedMacOSSigning(fn: (ValidatedMacOSSigningSettings) -> T): T? = - nonValidatedMacSigningSettings?.let { nonValidated -> - if (currentOS == OS.MacOS && nonValidated.sign.get()) { - fn(nonValidated.validate(nonValidatedMacBundleID)) - } else null - } + private val macSigner: MacSigner? by lazy { + val nonValidatedSettings = nonValidatedMacSigningSettings + if (currentOS == OS.MacOS && nonValidatedSettings?.sign?.get() == true) { + val validatedSettings = nonValidatedSettings.validate(nonValidatedMacBundleID) + MacSigner(validatedSettings, runExternalTool) + } else null + } @get:LocalState protected val signDir: Provider = project.layout.buildDirectory.dir("compose/tmp/sign") @@ -272,11 +273,11 @@ abstract class AbstractJPackageTask @Inject constructor( cliArg("--mac-package-name", macPackageName) cliArg("--mac-package-identifier", nonValidatedMacBundleID) - withValidatedMacOSSigning { signing -> + macSigner?.let { signer -> cliArg("--mac-sign", true) - cliArg("--mac-signing-key-user-name", signing.identity) - cliArg("--mac-signing-keychain", signing.keychain) - cliArg("--mac-package-signing-prefix", signing.prefix) + cliArg("--mac-signing-key-user-name", signer.settings.identity) + cliArg("--mac-signing-keychain", signer.settings.keychain) + cliArg("--mac-package-signing-prefix", signer.settings.prefix) } } } @@ -324,16 +325,12 @@ abstract class AbstractJPackageTask @Inject constructor( override fun prepareWorkingDir(inputChanges: InputChanges) { val libsDir = libsDir.ioFile val fileProcessor = - withValidatedMacOSSigning { signing -> + macSigner?.let { signer -> val tmpDirForSign = signDir.ioFile fileOperations.delete(tmpDirForSign) tmpDirForSign.mkdirs() - MacJarSignFileCopyingProcessor( - tempDir = tmpDirForSign, - execOperations = execOperations, - signing = signing - ) + MacJarSignFileCopyingProcessor(signer, tmpDirForSign) } ?: SimpleFileCopyingProcessor fun copyFileToLibsDir(sourceFile: File): File { val targetFileName = @@ -388,7 +385,8 @@ abstract class AbstractJPackageTask @Inject constructor( private fun patchInfoPlistIfNeeded() { if (currentOS != OS.MacOS || targetFormat != TargetFormat.AppImage) return - val infoPlist = destinationDir.ioFile.resolve("${packageName.get()}.app/Contents/Info.plist") + val appDir = destinationDir.ioFile.resolve("${packageName.get()}.app/") + val infoPlist = appDir.resolve("Contents/Info.plist") if (!infoPlist.exists()) return val content = infoPlist.readText() @@ -405,12 +403,13 @@ abstract class AbstractJPackageTask @Inject constructor( if (i >= 0) { val newContent = buildString { append(content.substring(0, i)) - appendln(stringToAppend) + appendLine(stringToAppend) append(" ") - appendln(content.substring(i, content.length)) + appendLine(content.substring(i, content.length)) } infoPlist.writeText(newContent) } + macSigner?.sign(appDir) } override fun initState() { diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/tasks/AbstractComposeDesktopTask.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/tasks/AbstractComposeDesktopTask.kt index ea9af5bb96..a6743d0921 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/tasks/AbstractComposeDesktopTask.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/tasks/AbstractComposeDesktopTask.kt @@ -16,7 +16,9 @@ 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.* import org.jetbrains.compose.desktop.application.internal.ComposeProperties +import org.jetbrains.compose.desktop.application.internal.ExternalToolRunner import org.jetbrains.compose.desktop.application.internal.alsoOutputTo import org.jetbrains.compose.desktop.application.internal.ioFile import org.jetbrains.compose.desktop.application.internal.notNullProperty @@ -48,71 +50,7 @@ abstract class AbstractComposeDesktopTask : DefaultTask() { }) } - internal fun runExternalTool( - tool: File, - args: Collection, - environment: Map = emptyMap(), - workingDir: File? = null, - checkExitCodeIsNormal: Boolean = true, - processStdout: Function1? = null, - forceLogToFile: Boolean = false - ): ExecResult { - val logsDir = logsDir.ioFile - logsDir.mkdirs() - - val toolName = tool.nameWithoutExtension - val logToConsole = verbose.get() && !forceLogToFile - val outFile = logsDir.resolve("${toolName}-${currentTimeStamp()}-out.txt") - val errFile = logsDir.resolve("${toolName}-${currentTimeStamp()}-err.txt") - - val result = outFile.outputStream().buffered().use { outFileStream -> - errFile.outputStream().buffered().use { errFileStream -> - 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 (logToConsole) { - spec.standardOutput = spec.standardOutput.alsoOutputTo(outFileStream) - spec.errorOutput = spec.errorOutput.alsoOutputTo(errFileStream) - } else { - spec.standardOutput = outFileStream - spec.errorOutput = errFileStream - } - } - } - } - - 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")) + @get:Internal + internal val runExternalTool: ExternalToolRunner + get() = ExternalToolRunner(verbose, logsDir, execOperations) } \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/gradle/DesktopApplicationTest.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/gradle/DesktopApplicationTest.kt index fec811889f..bc4ab77e61 100644 --- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/gradle/DesktopApplicationTest.kt +++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/gradle/DesktopApplicationTest.kt @@ -7,10 +7,7 @@ package org.jetbrains.compose.gradle import org.gradle.internal.impldep.org.testng.Assert import org.gradle.testkit.runner.TaskOutcome -import org.jetbrains.compose.desktop.application.internal.OS -import org.jetbrains.compose.desktop.application.internal.currentArch -import org.jetbrains.compose.desktop.application.internal.currentOS -import org.jetbrains.compose.desktop.application.internal.currentTarget +import org.jetbrains.compose.desktop.application.internal.* import org.jetbrains.compose.test.* import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assumptions @@ -159,6 +156,55 @@ class DesktopApplicationTest : GradlePluginTestBase() { } } + @Test + fun testMacSign() { + Assumptions.assumeTrue(currentOS == OS.MacOS) + + fun security(vararg args: Any): ProcessRunResult { + val args = args.map { + if (it is File) it.absolutePath else it.toString() + } + return runProcess(MacUtils.security, args) + } + + fun withNewDefaultKeychain(newKeychain: File, fn: () -> Unit) { + val originalKeychain = + security("default-keychain") + .out + .trim() + .trim('"') + + try { + security("default-keychain", "-s", newKeychain) + fn() + } finally { + security("default-keychain", "-s", originalKeychain) + } + } + + with(testProject(TestProjects.macSign)) { + val keychain = file("compose.test.keychain") + val password = "compose.test" + + withNewDefaultKeychain(keychain) { + security("default-keychain", "-s", keychain) + security("unlock-keychain", "-p", password, keychain) + + gradle(":createDistributable").build().checks { check -> + check.taskOutcome(":createDistributable", TaskOutcome.SUCCESS) + val appDir = testWorkDir.resolve("build/compose/binaries/main/app/TestPackage.app/") + val result = runProcess(MacUtils.codesign, args = listOf("--verify", "--verbose", appDir.absolutePath)) + val actualOutput = result.err.trim() + val expectedOutput = """ + |${appDir.absolutePath}: valid on disk + |${appDir.absolutePath}: satisfies its Designated Requirement + """.trimMargin().trim() + Assert.assertEquals(expectedOutput, actualOutput) + } + } + } + } + @Test fun testOptionsWithSpaces() { with(testProject(TestProjects.optionsWithSpaces)) { diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/TestProjects.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/TestProjects.kt index 8692f43732..d8b611a964 100644 --- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/TestProjects.kt +++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/TestProjects.kt @@ -12,6 +12,7 @@ object TestProjects { const val moduleClashCli = "application/moduleClashCli" const val javaLogger = "application/javaLogger" const val macOptions = "application/macOptions" + const val macSign = "application/macSign" const val optionsWithSpaces = "application/optionsWithSpaces" const val unpackSkiko = "application/unpackSkiko" const val jsMpp = "misc/jsMpp" diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/fileUtils.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/fileUtils.kt index a0d87d9097..ced8ea19a5 100644 --- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/fileUtils.kt +++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/fileUtils.kt @@ -16,11 +16,11 @@ fun File.modify(fn: (String) -> String) { fun File.checkExists(): File = apply { check(exists()) { buildString { - appendln("Requested file does not exist: $absolutePath") + appendLine("Requested file does not exist: $absolutePath") parentFile?.listFiles()?.let { siblingFiles -> - appendln("Other files in the same directory: ${parentFile.absolutePath}") + appendLine("Other files in the same directory: ${parentFile.absolutePath}") siblingFiles.forEach { - appendln(" * ${it.name}") + appendLine(" * ${it.name}") } } } diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/processUtils.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/processUtils.kt new file mode 100644 index 0000000000..f310e9a048 --- /dev/null +++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/processUtils.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. + */ + +package org.jetbrains.compose.test + +import java.io.File + +internal data class ProcessRunResult(val exitCode: Int, val out: String, val err: String) + +internal fun runProcess( + tool: File, + args: Collection, + checkExitCodeIsNormal: Boolean = true +): ProcessRunResult { + val outFile = File.createTempFile("run-process-compose-tests-out.txt", null).apply { deleteOnExit() } + val errFile = File.createTempFile("run-process-compose-tests-err.txt", null).apply { deleteOnExit() } + return try { + val cmd = arrayOf(tool.absolutePath, *args.toTypedArray()) + val process = ProcessBuilder().run { + redirectError(errFile) + redirectOutput(outFile) + command(*cmd) + start() + } + val exitCode = process.waitFor() + if (checkExitCodeIsNormal) { + check(exitCode == 0) { + buildString { + appendLine("Non-zero exit code: $exitCode") + appendLine("Command: ${cmd.joinToString(", ")}") + appendLine("Out:") + outFile.forEachLine { line -> + appendLine(" >$line") + } + appendLine("Err:") + errFile.forEachLine { line -> + appendLine(" >$line") + } + } + } + } + ProcessRunResult(exitCode = exitCode, out = outFile.readText(), err = errFile.readText()) + } finally { + outFile.delete() + errFile.delete() + } +} diff --git a/gradle-plugins/compose/src/test/test-projects/application/macSign/build.gradle b/gradle-plugins/compose/src/test/test-projects/application/macSign/build.gradle new file mode 100644 index 0000000000..2e13dd5860 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/application/macSign/build.gradle @@ -0,0 +1,37 @@ +import org.jetbrains.compose.desktop.application.dsl.TargetFormat + +plugins { + id "org.jetbrains.kotlin.jvm" + id "org.jetbrains.compose" +} + +repositories { + google() + mavenCentral() + maven { + url "https://maven.pkg.jetbrains.space/public/p/compose/dev" + } +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib" + implementation compose.desktop.currentOs +} + +compose.desktop { + application { + mainClass = "MainKt" + nativeDistributions { + packageName = "TestPackage" + macOS { + bundleID = "signing.test.package" + + signing { + sign.set(true) + identity.set("Compose Test") + keychain.set(project.file("compose.test.keychain").absolutePath) + } + } + } + } +} diff --git a/gradle-plugins/compose/src/test/test-projects/application/macSign/compose.test.keychain b/gradle-plugins/compose/src/test/test-projects/application/macSign/compose.test.keychain new file mode 100644 index 0000000000000000000000000000000000000000..e5c2af150eab88ab581ace203fd6295ba328439e GIT binary patch literal 25820 zcmeHPd013s+dnhR4Ev7Y0s?M;fDD8iQi6bpf(wiLHo&0du*9NJnnbvzCiP{xZ{bpI z^{S<&W~phZY459LWol-bX_~3IWqrT<%z5xQo*B-$-mdSj@4l{^Irnp(-|xAf<=p2! z&lzSyb>4VR#gTYGefT3Te?LB5pxrpmE}!FCm2zCh&m7kubq37jxM4UqypH2WXgF?U zJLm`ZVvP~$EnbcY9zD{x&eBEZCS_%1j7d%(liX`eR#KmojKnc%{d*7K&nDw9VXfiSIOqR3J~2BxGp$!n zc1jk`69?6^#zEyR9_L8wpPVw3C%}31iLdiPeBd&h3``hf9Y^-9_Ja_S>Jt3Qc7laTJN+ZIL1S1 zY#`0)nxOs-$44D(#x2;Y{g^70sgt2~P}p{`?O@x%wu7yKt%0q9t%0qDt%a?Ht%a?F zt%I$Dt%Ge3+a9((Y?F`!)wli#J*e+a0z$YD~B!S;h~HO55Z zTr{*Zv^(_48vG$K36 z`(ixCM3(>Lr&Rx?VvSV$Jbt37$g=-ZF~}<3ke^>hen6-X2~ernWEEdtUN{aI5{8$G zRo3uYiSd_;Q`Yc0q5tCXmx@o;@b*Ig#dwNKWQ)Zb-a+WU7|-HRQ+U1&iS_Rapq%#k zI_3FA#X>`5CqUhx^6_O8E$5%^C$jOS6?rDqCt>Z=y+k(NP*GjVqaWhw9wHlGT2f{i zKfDy-4He@p+V@s~caT`W$i-y)?5t$ z;w1^g2Mfo1q438R-c_i>@NE>}3B&G}4Bu7(UX8je-Y|SS1$fFeSiE8Q_6qQnYp{63 z@EsK3Dc4}}hT%gL;A!kwykYoI1$er4SiE8QFa>zJc38Y&_>KzjbnUQs!|>q>@O164 zc*F1!GVyeuB(aP?wAP7BEK=*A;Vt7&Cjgb;E#ps=LcC@C(JRDT#vgi4P~yL3{E1PB zw~RkeD8yUFpI8NW>ZfJ=p=Tty{g;YAT@>J@;!jrvc&Ye zyu4Cqh}LofG!rTtUtXMBhWaE7Pwy$@;#GqGgk|__1$ec=R9G%x89qk=-mbhd_h}3K zU4a(#UbvUURH2MwQv*IWa9m6& zj!6Vt{U^40ah=*NfnLFMCqoxNajZ>%)mhpx)*!<9#HhtVFMHx zN7QpPL18<>c7p8$+i4f}_2H8)*j})`V0*##g6$348@BgW^P{2<`sY&v zML+1#uNmxSu$#fAM>${EzOa2^`?)|zLf1j>;880X`Wo~c9-CsJbv>2|+s{XeXor zih7}_7g`TJ&2eF9Ck+0C9pSi+Xs6?N=q`>6_kg0E@D0#EIWA%#bOjXnNZ=#qKu>X8 zry!^a3QQC*QD`R$?L_^?aeCC#*F#U^HApD50t$a({5g&TA1`zbi6t5aJqu7iOgSM5 z-5bQ>lxZ{>Dhf-A(~|jOTJyxmu19fQTYRi-7sl7$(D+_~_=uO)VVu_Zs4f$0{?UD3 z2_LKLoLEs&R+w8^VKjG`+QK;V@tW0PZATvIpvLXYf#(X{-pOhcS z;TSRtrxoWLs~*lb`9^sMJC8$Nq~@FX#^zgU{SY7J@JjgD zJUjBfWmlIP%|5X8=pvJk)+4pc=GdF(Gv+57Dh%l*dHkhK%*0E6x%>AtjgSSW7)*QJY(a6 zA-H6ARC`=EYCxIYM6fhpf=t|FViQ6cC;?0ooiYyL~)z(B&$|w@`vF&nqhx@YE))RkjC= z!$uMN-+2G7F`uS*P2xDPtgyJ^;mwxDlddDFcTiG%X*H+}p;%3#O*9o6=z#*l=7w?% z*<({!Jh2e16S0F`ua<}n?UdjpVncf+c!}81K?z8x8t(&G|L%2*niVehPD>g(l!G+Vj(6z0W zhz*fAEBD$`OT-4LcU9J|ZM8&fh*HwNL~PJ2!Aryj@>O>Kh3_hfV?&G*yzpHQG5!f9 zc;UMqgeOga#zJNLFML-@+&(>@%Ek-dl@jB-D8Q>EVw=>vDr@UUB@x@YDQRCKwslv6 zmxygr@2af&FA>|M-c?z}OT@N#CH_mqHi})g=3gA!s9saCP3qm1xIc`KVjJ<aaMo7ecrgC(c!}7Ss01$&+j=R%OT@M$C3uP0maGIXe2-0hZKo*0 zo4>~<#`ji&mxyhtO7Oz>*u?GkQGyq~$3}S41Smb@%3eRh_t;qb^bF9q34G(VfO_=Y zO+t8we#Yt~lcAg*1IVW56RCGv;%k9%WuPv5h9|7G=M(bJR^J#OjREly7pdj*3He5C z%Hd+Z(KEH#HzT&|ijBpT@C#++m(=?&slGDb=sBPG?C~r%(NIB;hP+L_4S*`~O)FrX zu%A&_P-w_C@pL>N`9_>__?U0>48V_Dx*^wSf&rnjZ0I1U94@Lyb3n0|xZDy;OHGC5 zy`^l!IFy(<$+t|X98NYD6n}}+iPl`1(NIxBPv&Mm>TecQ4j=2UBkJ>W0Y0`#LN@i6 zR1O#GFYOT$))ib?h0hw(ihC7S2+zihlkLBuTBAs<6~;;XgoO1-DJU@JRTNG%3gkjL zjp1OZ96#9@x(HautO`R}1$O?-ONO6k8bf*?Cx?%Xp{u}WpHfWwh&&su8S;(x%H?n| z-)Mi3`sw2iV{t5Jjj*wzy+p$5 zu!UP#P>mzAO>4sdmBYo>2JI^nmoxd4Y0N9ZjKbv0D4yg(i*yUE;EqOVAxi4j=Q4xJadPiSaVIm~Tzx5`{APm~XaniKk_9G2gVPPyMu( zOH7c-$9%JuOPFMG(RDy`DkqmHlF7;X+f*(=F-6HbaYTI@KYNK>qC_Sa>#v+#g05XT z{xMEDxx_OvIoTMxpg#54S}sAsPmYgl&A1ADQn>`BQgXPMZ%ySAbW@bW$Hq@iE>S6y zlle$%o5s&tE-_IiAM?>u;IoxWOp?jT=EPPmQKgW}RxUwzUODr@{IZoxJS&rn?uj%9 z)^dsGWb!bdY~>P@Wpc5#(Nr!mMJ69x8**}q=VfxTakG|7OqI#Q#?4kPQKNv1);|ed zQ&PFa3n-Vf#%Kd`Fm6J=% zR>&zQmzX1ylZ|0hxx`$Vd~6I|QJ)_}iG7KeWpXj!n#v{S$>d{mA}5!qm&wU|Y$}&{ zMJ6Bf(G&G){A}eCugc_Pb7Cu(_#cH_wsMKr6mr?hCFaZIqI)8ZnYCQvb(uVDtZd~H zZ^-0gYon=LVu4IPwl?JC5({N=vT?JPODvMf!^X{4F0mL~be|JTG>p#B?$E~f2sKCx zh5xmnYrrANSXLoy5R*L+x&$hRD}(1U7SlcrVO@v|`*PT^FQ&Z+#7aY@B~nChs8P75>MBcwGlp=HlH6X!{t-X|oAJzEZwH5(=d}kR(XZ0DQ6> zSE#3|LK)q2NP+|v)l*U5>7Gu$&V;UjZigO+{%odGHOHqO+6Ouo<*`r_YKz(-zbLn& zV@D|E9mGlZbyp|}I2wH;9AQYPocc>b<#eAQp>ozgDyNuFLgj1?QMnHlLa3Z_PZBDp zSWH6YtSu_1*h)g>Om6J_EpVT)JDyNu5Lgmz75-O({1fk#I!i1X3 zf*v--*MY4&hjS#x8aY?i9bA_YTR_<>It8BGCOI{@3`pune%^MIQx5-=r8?eb$oYP@h3@L zqW|dq;6nT!fA9BpZ49p)*)#d;BF)b?+s&J@vi+y_K~>i;CZ=?*>@n%gDaXFENqLN6xMorCFj0ee2hTSqBf*-%8pvZ1eL~?QV{U3u`@U z!uzY&tM%$vhb?r^T$fYXvDb-Kue`N$%j=UTH9L6asA+WG`vWSMZ+0*ExaftM=O$m= zm6oP;`Rtb6#@U@??VQ3pZ9TX?u1}q(pYHny-zIrZ4Lq~3+xrvOjQ`J1KlRQ{3^gTv zSl>0e^q49u|KN>jGjkSxx+yfI{8*bC)BU%9{Mr*MFBUEwc6&kKrgXpLh!OXWZjZds zKjX7=Q6Ie9^MqGQ^4idK9;1I~8?pZGrB6O=aly6sV$UyIW;x$D^ij73{dXN4r*V8qp^TFV5^J-2d4?1Ank@wa{?d0NrFUk3P$o=&nJkz&I zd(hrt_r9nDk$e8yc>2XrA1wIGWlY=~&$peua&E}=scZha@J9H*O0EqVH+gGt+^};I zBivRD?b{*l+T3x0m$oLIDcC$Kc4O)vnTvPjyMOcEsNio5(+?cl@3x|0N6FFFA1t1^ za>Dfa+r4*r&JXx~U!O{Kz}A+>4sTsL@9whe58RJLJ<03;|8|Ex2d{iF^R#JKY() z)|j{%!~3n7P;~p%&-Si<>hk9S`Og)^U+CK4^~sD^)=zk*+%0nbt=G2}MD<=Y>G+dZ zV*DqyTGA%_rJB(LeCr*;r+<6L@I&~HP4mn-1o*A z>KaNnOm6=Bgz-r`KiGZWV{=Q@GavjO5qj#+ohzIxk9Hrpb<5Y?3^Om?AMFtM?6*E8 zk>^7X>jH<*xo_w&z0a8`$@NEC9EsV~L-Y6V+QdAgPrqLGRd2SuF=0o~q_eC14s8ED zx6O=hsdFlRPC0D69y~RF;HT%S&dxhL{fkMa6ASLmT-ELN`D@3vbXiuGn_05_a^TYL zr*8f_IB(zCSH3F0`&-!e8DrX%E=wQMYTvd`oI5Xmc5VH)uOA)Y^IOP;7yngOH)qPN zoz1tbTif%(-K&R(#B|ZNPrguIwdQ<}eIbRHGkqgE9eS=ZD%8QJ=YU^^#;3$A4(rf& z;j)A3aijc3wC(!CNe5%gXYQ_<^75#Ng1;)$#_ZV~)$6>As_&cOT^FZJ?meW|Vd|;x zQ*S(IGjv7f;?QZ?8FBH`l7e+tJu=lZ&LgzZy`e)g-us$LN{wYfX~{vNFH6J)C6yGF zmXsTVvW?{xd`zT2EI!h)k2<2*-tE@c<$))^?|-;B=fLRGorCEFB;>hnU8B24=NV!m z^#ys|Bcr49V~GnB2}W(J?Vo__~Pa6wCQ|&hC43LVQf^GNp6u*v6-t z?tjPu?2#+jp97sgG#QHA)!{YhF6af^Cy^sKqK@N8)ZYLlAzo^~sk{R9>`Ms%0q_6m zn8c$!@{@!<$yavo5YET^ z=`e>1AmVzKITG=0HCKD2o@LHN^-fFFv&^ATkA91k7je66V8}lj8>$y!uJ%Z~mN^#H z%a*8TnR8LS$&c3Kb6?6kNP+|nz$faG{D1CCg8==9b6>8e2Bd-n;geJ}?%Y_|w61a8 z^Uu@c@#zmGA%E2L9Z}Kmtwu4Yl)IK%AU#NZX zSp}_!u7mD{o`U`cIIUMbl!O5GyifQIO!{s-eFv1j$4TGC6uwVM^~fK>k*F;EY+To( z-_fM{jq6zW&OKqNoSvUZRKoX7>6pr0=m4S;{hlS2v*#?8=yxiqoc1qCR3bn3);x?q zK{!V$8Xs;2Y>E>UkH$i)plpoV;h2QRL_>X~|23fR>x+J$mD+8LGZy2}7*|BUCrkAk z0Nb>9jEu1e%n>0a@KPCr=p|ukvh8^jh&0kdv~<6-jM)0 zZ*^vlzO|ogOte0hRNp1G%Wyx}Ct`6Y?k<@BHGi$Djo4+Cmb0r>xpVqjm6N(wrQ$A> z3@%(=d1%vtq}_QR?&@9mWVe9aOIHu}uit**=now{->pr3C41rLJ-0mfZq%hGTIjZV zuXS>(xHjiMD`E`;YPMaRyRq}s&wsN2u6Td=!Q5?$v6n9$Yw^SIu;;#cPTwc1+4i45 zzA~iMt)&mDUi4V7&1HUNT=AY&A9wKg_VAvw^6G@Q_BZS;i*CNE;_mmyHs0y`OV#ie z85gJDTQP6%p%E{xo9lnjckGng&p!3i@2h8?3*GzoYd?Lp^!Aghf+Ab~GUHO~(bY#T z{ra}<`qhm;92}Z@pjULeAGyinmzlDC*169OpD^)$YOvd)z8OCC#)4;O)Rzsp;B>6* zwdfdDsIWFYhGW~4@@4gd(dmEs+MkAwZ`w?Gve${?Y#TZ`I7F{ z5rb=;FMZT2eebJV>!<&f{bWFVrbEQJfS1?r+Vwn%etjp^+j{UQqg75-kQZF$-LcaPTIY85~0^0niC2YTP!nQC|3 zV}5Plz9Vx2#>C~0JJBX+*5T8$qx;^ipF3)9+e2MO%=@s;`9}CJflr0IcKfkk*T|ik z75l%sQBZm#>U1o3V)M(x!h41eSQNMV1+PKz%lF+ndZ0GQ?VBm_=i@%QzVGwsZ-VwW z_jQ`uY)b9iw~Ygahy1DSR{Km)vfsrA8v{FK>q_6apRSozW4>7ETBDdO^E%)`O6n&3 zw-*Ci{3kdt;k;PM<U_ULx>U$sCSH{l@#Y zus4i$w1>=eTrxiYiGET$)CUsP!`~Uw>CHv|SUc2z;hw^&9?qR^fzXZvwJ)rf<~VM= z*9rT@R4pFbc&~f-n?gD^|Ae!$*0`T*xEh@1=PC%F=b$9iWDs;OltgfW&uKW(J|E5F zx*J(les`n}IX&F!%^>GK`>)4utT_eRY>6CW14bJfxx vN2kW56m$(6kh-*!)Ao(uAK^+Wm;Cz2z0$*nl6TF!c&EqeX@Jo9k!b%H&)$PP literal 0 HcmV?d00001 diff --git a/gradle-plugins/compose/src/test/test-projects/application/macSign/settings.gradle b/gradle-plugins/compose/src/test/test-projects/application/macSign/settings.gradle new file mode 100644 index 0000000000..0fcbea6df4 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/application/macSign/settings.gradle @@ -0,0 +1,10 @@ +pluginManagement { + plugins { + id 'org.jetbrains.kotlin.jvm' version 'KOTLIN_VERSION_PLACEHOLDER' + id 'org.jetbrains.compose' version 'COMPOSE_VERSION_PLACEHOLDER' + } + repositories { + mavenLocal() + gradlePluginPortal() + } +} diff --git a/gradle-plugins/compose/src/test/test-projects/application/macSign/src/main/kotlin/main.kt b/gradle-plugins/compose/src/test/test-projects/application/macSign/src/main/kotlin/main.kt new file mode 100644 index 0000000000..e97bc7ec75 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/application/macSign/src/main/kotlin/main.kt @@ -0,0 +1,2 @@ +fun main() { +} \ No newline at end of file