Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,11 @@ internal class MacSignerImpl(
}

private fun matchCertificates(certificates: String): String {
val regex = Pattern.compile("\"alis\"<blob>=\"([^\"]+)\"")
// When the developer id contains non-ascii characters, the output of `security find-certificate` is
// slightly different. The `alis` line first has the hex-encoded developer id, then some spaces,
// and then the developer id with non-ascii characters encoded as octal.
// See https://bugs.openjdk.org/browse/JDK-8308042
val regex = Pattern.compile("\"alis\"<blob>=(0x[0-9A-F]+)?\\s*\"([^\"]+)\"")
val m = regex.matcher(certificates)
if (!m.find()) {
val keychainPath = settings.keychain?.absolutePath
Expand All @@ -102,14 +106,24 @@ internal class MacSignerImpl(
" 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
val hexEncoded = m.group(1)
if (hexEncoded.isNullOrBlank()) {
// Regular case; developer id only has ascii characters
val result = m.group(2)
if (m.find())
error(
"Multiple matching certificates are found for '${settings.fullDeveloperID}'. " +
"Please specify keychain containing unique matching certificate."
)
return result
} else {
return hexEncoded
.substring(2)
.chunked(2)
.map { it.toInt(16).toByte() }
.toByteArray()
.toString(Charsets.UTF_8)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -366,19 +366,34 @@ class DesktopApplicationTest : GradlePluginTestBase() {
}
}

private fun macSignProject(
identity: String,
keychainFilename: String,
javaVersion: String = "17"
) = testProject("application/macSign").apply {
modifyText("build.gradle") {
it
.replace("%IDENTITY%", identity)
.replace("%KEYCHAIN%", keychainFilename)
.replace("%JAVA_VERSION%", javaVersion)
}
}

@Test
fun testMacSignConfiguration() {
Assumptions.assumeTrue(currentOS == OS.MacOS)

with(testProject("application/macSign")) {
with(macSignProject(identity = "Compose Test", keychainFilename = "compose.test.keychain")) {
gradle("--dry-run", ":createDistributable")
}
}

@Test
@Disabled
// the test does not work on CI and locally unless test keychain is opened manually
fun testMacSign() {
private fun testMacSign(
identity: String,
keychainFilename: String,
keychainPassword: String,
javaVersion: String = "17"
) {
Assumptions.assumeTrue(currentOS == OS.MacOS)

fun security(vararg args: Any): ProcessRunResult {
Expand All @@ -403,13 +418,12 @@ class DesktopApplicationTest : GradlePluginTestBase() {
}
}

with(testProject("application/macSign")) {
val keychain = file("compose.test.keychain")
val password = "compose.test"
with(macSignProject(identity = identity, keychainFilename = keychainFilename, javaVersion = javaVersion)) {
val keychain = file(keychainFilename)

withNewDefaultKeychain(keychain) {
security("default-keychain", "-s", keychain)
security("unlock-keychain", "-p", password, keychain)
security("unlock-keychain", "-p", keychainPassword, keychain)

gradle(":createDistributable").checks {
check.taskSuccessful(":createDistributable")
Expand All @@ -431,6 +445,29 @@ class DesktopApplicationTest : GradlePluginTestBase() {
}
}

@Test
@Disabled
// the test does not work on CI and locally unless test keychain is opened manually
fun testMacSign() {
testMacSign(
identity = "Compose Test",
keychainFilename = "compose.test.keychain",
keychainPassword = "compose.test"
)
}

@Test
@Disabled
// the test does not work on CI and locally unless test keychain is opened manually
fun testMacSignWithNonAsciiDeveloperId() {
testMacSign(
identity = "Cömpose Test",
keychainFilename = "compose.test-non-ascii.keychain",
keychainPassword = "compose.test",
javaVersion = "21", // https://bugs.openjdk.org/browse/JDK-8308042 fixed in JDK 21
)
}

@Test
fun testOptionsWithSpaces() {
with(testProject("application/optionsWithSpaces")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,18 @@ compose.desktop {
application {
mainClass = "MainKt"
nativeDistributions {
javaHome = javaToolchains.launcherFor {
languageVersion.set(JavaLanguageVersion.of(%JAVA_VERSION%))
}.get().metadata.installationPath.asFile.absolutePath

packageName = "TestPackage"
macOS {
bundleID = "signing.test.package"

signing {
sign.set(true)
identity.set("Compose Test")
keychain.set("compose.test.keychain")
identity.set("%IDENTITY%")
keychain.set("%KEYCHAIN%")
}
}
}
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pluginManagement {
id 'org.jetbrains.kotlin.jvm' version 'KOTLIN_VERSION_PLACEHOLDER'
id 'org.jetbrains.kotlin.plugin.compose' version 'KOTLIN_VERSION_PLACEHOLDER'
id 'org.jetbrains.compose' version 'COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER'
id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a plugin that automatically downloads and "installs" JDKs as needed. We need it because the tests now include testing with a JDK not installed by default (JDK 21, in testMacSignWithNonAsciiDeveloperId)

}
repositories {
mavenLocal()
Expand All @@ -14,6 +15,9 @@ pluginManagement {
}
}
}
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0'
}
dependencyResolutionManagement {
repositories {
mavenCentral()
Expand Down