Browse Source

Merge branch 'JetBrains:master' into settings-property-fix

pull/5095/head
Maxim Pestryakov 1 week ago committed by GitHub
parent
commit
eb0197f528
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 8
      .github/PULL_REQUEST_TEMPLATE.md
  2. 7
      README.md
  3. 78
      build-helpers/build.gradle.kts
  4. 1
      build-helpers/gradle.properties
  5. 51
      build-helpers/publishing/build.gradle.kts
  6. 1
      build-helpers/settings.gradle.kts
  7. 74
      ci/build-helpers/build.gradle.kts
  8. 26
      ci/build-helpers/buildSrc/build.gradle.kts
  9. 0
      ci/build-helpers/buildSrc/src/main/kotlin/org/jetbrains/compose/internal/publishing/DownloadFromSpaceTask.kt
  10. 25
      ci/build-helpers/buildSrc/src/main/kotlin/org/jetbrains/compose/internal/publishing/FindModulesInSpaceTask.kt
  11. 0
      ci/build-helpers/buildSrc/src/main/kotlin/org/jetbrains/compose/internal/publishing/FixModulesBeforePublishingTask.kt
  12. 10
      ci/build-helpers/buildSrc/src/main/kotlin/org/jetbrains/compose/internal/publishing/MavenCentralProperties.kt
  13. 0
      ci/build-helpers/buildSrc/src/main/kotlin/org/jetbrains/compose/internal/publishing/ModuleToUpload.kt
  14. 8
      ci/build-helpers/buildSrc/src/main/kotlin/org/jetbrains/compose/internal/publishing/UploadToSonatypeTask.kt
  15. 0
      ci/build-helpers/buildSrc/src/main/kotlin/org/jetbrains/compose/internal/publishing/utils/AbstractRestApiClient.kt
  16. 0
      ci/build-helpers/buildSrc/src/main/kotlin/org/jetbrains/compose/internal/publishing/utils/Checksum.kt
  17. 0
      ci/build-helpers/buildSrc/src/main/kotlin/org/jetbrains/compose/internal/publishing/utils/Json.kt
  18. 5
      ci/build-helpers/buildSrc/src/main/kotlin/org/jetbrains/compose/internal/publishing/utils/ModuleValidator.kt
  19. 0
      ci/build-helpers/buildSrc/src/main/kotlin/org/jetbrains/compose/internal/publishing/utils/PomDocument.kt
  20. 0
      ci/build-helpers/buildSrc/src/main/kotlin/org/jetbrains/compose/internal/publishing/utils/RequestError.kt
  21. 0
      ci/build-helpers/buildSrc/src/main/kotlin/org/jetbrains/compose/internal/publishing/utils/SonatypeApi.kt
  22. 0
      ci/build-helpers/buildSrc/src/main/kotlin/org/jetbrains/compose/internal/publishing/utils/SonatypeRestApiClient.kt
  23. 4
      ci/build-helpers/buildSrc/src/main/kotlin/org/jetbrains/compose/internal/publishing/utils/SpaceApiClient.kt
  24. 0
      ci/build-helpers/buildSrc/src/main/kotlin/org/jetbrains/compose/internal/publishing/utils/Xml.kt
  25. 0
      ci/build-helpers/gradle/wrapper/gradle-wrapper.jar
  26. 2
      ci/build-helpers/gradle/wrapper/gradle-wrapper.properties
  27. 0
      ci/build-helpers/gradlew
  28. 0
      ci/build-helpers/gradlew.bat
  29. 0
      ci/build-helpers/settings.gradle.kts
  30. 2
      components/gradle.properties
  31. BIN
      components/resources/demo/shared/src/commonMain/composeResources/font/RobotoFlex-VariableFont.ttf
  32. 66
      components/resources/demo/shared/src/commonMain/kotlin/org/jetbrains/compose/resources/demo/shared/FontRes.kt
  33. 7
      components/resources/library/build.gradle.kts
  34. 17
      components/resources/library/src/androidMain/kotlin/org/jetbrains/compose/resources/FontResources.android.kt
  35. 17
      components/resources/library/src/blockingMain/kotlin/org/jetbrains/compose/resources/ResourceState.blocking.kt
  36. 26
      components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/FontResources.kt
  37. 16
      components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ResourceState.kt
  38. 2
      components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/StringResourcesUtils.kt
  39. 181
      components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/StringFormatTest.kt
  40. 28
      components/resources/library/src/macosMain/kotlin/org/jetbrains/compose/resources/ResourceReader.macos.kt
  41. 32
      components/resources/library/src/skikoMain/kotlin/org/jetbrains/compose/resources/FontResources.skiko.kt
  42. 49
      components/resources/library/src/skikoTest/kotlin/org/jetbrains/compose/resources/VariationFontCacheTest.kt
  43. 9
      components/resources/library/src/webMain/kotlin/org/jetbrains/compose/resources/Resource.web.kt
  44. 20
      components/resources/library/src/webMain/kotlin/org/jetbrains/compose/resources/ResourceState.web.kt
  45. 7
      components/ui-tooling-preview/library/build.gradle.kts
  46. 15
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/IosResources.kt
  47. 12
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/IosResourcesTasks.kt
  48. 122
      gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/ResourcesTest.kt
  49. 3
      gradle-plugins/compose/src/test/test-projects/misc/commonResources/build.gradle.kts
  50. 2
      gradle-plugins/compose/src/test/test-projects/misc/kmpResourcePublication/appModule/build.gradle.kts
  51. 3
      gradle-plugins/compose/src/test/test-projects/misc/kmpResourcePublication/appModule/src/macosMain/composeResources/values/macos_strings.xml
  52. 10
      gradle-plugins/compose/src/test/test-projects/misc/kmpResourcePublication/appModule/src/macosMain/kotlin/me/sample/app/App.macos.kt
  53. 2
      gradle-plugins/compose/src/test/test-projects/misc/kmpResourcePublication/cmplib/build.gradle.kts
  54. 2
      gradle-plugins/compose/src/test/test-projects/misc/kmpResourcePublication/featureModule/build.gradle.kts
  55. 1
      gradle-plugins/compose/src/test/test-projects/misc/kmpResourcePublication/gradle.properties
  56. 32
      gradle-plugins/compose/src/test/test-projects/misc/macosResources/build.gradle.kts
  57. 54
      gradle-plugins/compose/src/test/test-projects/misc/macosResources/expected/macosResources.podspec
  58. 2
      gradle-plugins/compose/src/test/test-projects/misc/macosResources/gradle.properties
  59. 24
      gradle-plugins/compose/src/test/test-projects/misc/macosResources/settings.gradle.kts
  60. 36
      gradle-plugins/compose/src/test/test-projects/misc/macosResources/src/commonMain/composeResources/drawable/compose-multiplatform.xml
  61. 39
      gradle-plugins/compose/src/test/test-projects/misc/macosResources/src/commonMain/kotlin/App.kt
  62. 36
      gradle-plugins/compose/src/test/test-projects/misc/macosResources/src/macosMain/composeResources/drawable/icon.xml
  63. 3
      gradle-plugins/compose/src/test/test-projects/misc/testResources/build.gradle.kts
  64. 1
      gradle-plugins/compose/src/test/test-projects/misc/testResources/gradle.properties
  65. 2
      gradle-plugins/gradle.properties
  66. 216
      tools/changelog.main.kts

8
.github/PULL_REQUEST_TEMPLATE.md

@ -15,16 +15,18 @@ This should be tested by QA
## Release Notes ## Release Notes
<!-- <!--
Optional, if omitted - won't be included in the changelog If we definitely shouldn't add Release Notes, add only N/A.
Sections: Or enumerate sections, subsections and all changes.
Possible sections:
- Highlights - Highlights
- Known Issues - Known Issues
- Breaking Changes - Breaking Changes
- Features - Features
- Fixes - Fixes
Subsections: Possible subsections:
- Multiple Platforms - Multiple Platforms
- iOS - iOS
- Desktop - Desktop

7
README.md

@ -1,8 +1,7 @@
[![official project](http://jb.gg/badges/official.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub) [![official project](http://jb.gg/badges/official.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub)
[![Latest release](https://img.shields.io/github/v/release/JetBrains/compose-multiplatform?color=brightgreen&label=latest%20release)](https://github.com/JetBrains/compose-multiplatform/releases/latest) [![stable](https://img.shields.io/github/v/release/JetBrains/compose-multiplatform?sort=semver&display_name=release&label=stable&color=brightgreen)](https://github.com/JetBrains/compose-multiplatform/releases/latest)
[![Latest build](https://img.shields.io/github/v/release/JetBrains/compose-multiplatform?color=orange&include_prereleases&label=latest%20build)](https://github.com/JetBrains/compose-multiplatform/releases) [![prerelease](https://img.shields.io/github/v/release/JetBrains/compose-multiplatform?include_prereleases&filter=*-*&display_name=release&label=prerelease&color=blue)](https://github.com/JetBrains/compose-multiplatform/releases)
[![dev](https://img.shields.io/github/v/tag/JetBrains/compose-multiplatform?include_prereleases&sort=semver&filter=v*%2Bdev*&label=dev&color=orange)](https://github.com/JetBrains/compose-multiplatform/tags)
# Compose Multiplatform # Compose Multiplatform

78
build-helpers/build.gradle.kts

@ -1,78 +0,0 @@
plugins {
kotlin("jvm") version "1.5.30" apply false
id("com.github.johnrengelman.shadow") version "7.1.0" apply false
}
subprojects {
group = "org.jetbrains.compose.internal.build-helpers"
version = project.property("deploy.version") as String
repositories {
mavenCentral()
}
plugins.withType(JavaBasePlugin::class.java) {
afterEvaluate {
configureIfExists<JavaPluginExtension> {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
if (sourceSets.names.contains(SourceSet.MAIN_SOURCE_SET_NAME)) {
withJavadocJar()
withSourcesJar()
}
}
}
}
plugins.withId("maven-publish") {
configureIfExists<PublishingExtension> {
configurePublishing(project)
}
}
}
fun PublishingExtension.configurePublishing(project: Project) {
repositories {
configureEach {
val repoName = name
project.tasks.register("publishTo${repoName}") {
group = "publishing"
dependsOn(project.tasks.named("publishAllPublicationsTo${repoName}Repository"))
}
}
maven {
name = "BuildRepo"
url = uri("${rootProject.buildDir}/repo")
}
maven {
name = "ComposeInternalRepo"
url = uri(
System.getenv("COMPOSE_INTERNAL_REPO_URL")
?: "https://maven.pkg.jetbrains.space/public/p/compose/internal"
)
credentials {
username =
System.getenv("COMPOSE_INTERNAL_REPO_USERNAME")
?: System.getenv("COMPOSE_REPO_USERNAME")
?: ""
password =
System.getenv("COMPOSE_INTERNAL_REPO_KEY")
?: System.getenv("COMPOSE_REPO_KEY")
?: ""
}
}
}
publications {
create<MavenPublication>("main") {
groupId = project.group.toString()
artifactId = project.name
version = project.version.toString()
from(project.components["java"])
}
}
}
inline fun <reified T> Project.configureIfExists(fn: T.() -> Unit) {
extensions.findByType(T::class.java)?.fn()
}

1
build-helpers/gradle.properties

@ -1 +0,0 @@
deploy.version=0.1.0-SNAPSHOT

51
build-helpers/publishing/build.gradle.kts

@ -1,51 +0,0 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.gradle.kotlin.dsl.gradleKotlinDsl
plugins {
`java`
`maven-publish`
`java-gradle-plugin`
id("org.jetbrains.kotlin.jvm")
id("com.github.johnrengelman.shadow") apply false
}
repositories {
maven("https://maven.pkg.jetbrains.space/public/p/space/maven")
}
val embeddedDependencies by configurations.creating { isTransitive = false }
dependencies {
compileOnly(gradleApi())
compileOnly(gradleKotlinDsl())
compileOnly(kotlin("stdlib"))
fun embedded(dep: String) {
compileOnly(dep)
embeddedDependencies(dep)
}
val jacksonVersion = "2.12.5"
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-xml:$jacksonVersion")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion")
implementation("io.ktor:ktor-client-okhttp:1.6.4")
implementation("org.apache.tika:tika-parsers:1.24.1")
implementation("org.jsoup:jsoup:1.14.3")
implementation("org.jetbrains:space-sdk-jvm:83821-beta")
embedded("de.undercouch:gradle-download-task:4.1.2")
}
val shadowJar by tasks.registering(ShadowJar::class) {
val fromPackage = "de.undercouch"
val toPackage = "org.jetbrains.compose.internal.publishing.$fromPackage"
relocate(fromPackage, toPackage)
archiveClassifier.set("shadow")
configurations = listOf(embeddedDependencies)
from(sourceSets["main"]!!.output)
exclude("META-INF/gradle-plugins/de.undercouch.download.properties")
}
val jar = tasks.named<Jar>("jar") {
dependsOn(shadowJar)
from(zipTree(shadowJar.get().archiveFile))
this.duplicatesStrategy = DuplicatesStrategy.INCLUDE
}

1
build-helpers/settings.gradle.kts

@ -1 +0,0 @@
include(":publishing")

74
ci/build-helpers/build.gradle.kts

@ -0,0 +1,74 @@
import org.jetbrains.compose.internal.publishing.*
plugins {
signing
}
val mavenCentral = MavenCentralProperties(project)
if (mavenCentral.signArtifacts) {
signing.useInMemoryPgpKeys(
mavenCentral.signArtifactsKey.get(),
mavenCentral.signArtifactsPassword.get()
)
}
val publishingDir = project.layout.buildDirectory.dir("publishing")
val originalArtifactsRoot = publishingDir.map { it.dir("original") }
val preparedArtifactsRoot = publishingDir.map { it.dir("prepared") }
val modulesFile = publishingDir.map { it.file("modules.txt") }
val findComposeModules by tasks.registering(FindModulesInSpaceTask::class) {
requestedCoordinates.set(mavenCentral.coordinates)
spaceInstanceUrl.set("https://public.jetbrains.space")
spaceClientId.set(System.getenv("COMPOSE_REPO_USERNAME") ?: "")
spaceClientSecret.set(System.getenv("COMPOSE_REPO_KEY") ?: "")
spaceProjectId.set(System.getenv("COMPOSE_DEV_REPO_PROJECT_ID") ?: "")
spaceRepoId.set(System.getenv("COMPOSE_DEV_REPO_REPO_ID") ?: "")
modulesTxtFile.set(modulesFile)
}
val downloadArtifactsFromComposeDev by tasks.registering(DownloadFromSpaceMavenRepoTask::class) {
dependsOn(findComposeModules)
modulesToDownload.set(project.provider {
readComposeModules(
modulesFile,
originalArtifactsRoot
)
})
spaceRepoUrl.set("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
val fixModulesBeforePublishing by tasks.registering(FixModulesBeforePublishingTask::class) {
dependsOn(downloadArtifactsFromComposeDev)
inputRepoDir.set(originalArtifactsRoot)
outputRepoDir.set(preparedArtifactsRoot)
}
val reuploadArtifactsToMavenCentral by tasks.registering(UploadToSonatypeTask::class) {
dependsOn(fixModulesBeforePublishing)
modulesToUpload.set(project.provider { readComposeModules(modulesFile, preparedArtifactsRoot) })
sonatypeServer.set("https://oss.sonatype.org")
user.set(mavenCentral.user)
password.set(mavenCentral.password)
autoCommitOnSuccess.set(mavenCentral.autoCommitOnSuccess)
stagingProfileName.set(mavenCentral.stage)
stagingDescription.set(mavenCentral.description)
}
fun readComposeModules(
modulesFile: Provider<out FileSystemLocation>,
repoRoot: Provider<out FileSystemLocation>
): List<ModuleToUpload> =
modulesFile.get().asFile.readLines()
.filter { it.isNotBlank() }
.map { line ->
val (group, artifact, version) = line.split(":")
ModuleToUpload(
groupId = group,
artifactId = artifact,
version = version,
localDir = repoRoot.get().asFile.resolve("$group/$artifact/$version")
)
}

26
ci/build-helpers/buildSrc/build.gradle.kts

@ -0,0 +1,26 @@
import org.gradle.kotlin.dsl.gradleKotlinDsl
plugins {
`java`
`java-gradle-plugin`
kotlin("jvm") version "1.9.24"
}
repositories {
mavenCentral()
maven("https://maven.pkg.jetbrains.space/public/p/space/maven")
}
dependencies {
compileOnly(gradleApi())
compileOnly(gradleKotlinDsl())
val jacksonVersion = "2.12.5"
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-xml:$jacksonVersion")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion")
implementation("io.ktor:ktor-client-okhttp:1.6.4")
implementation("org.apache.tika:tika-parsers:1.24.1")
implementation("org.jsoup:jsoup:1.14.3")
implementation("org.jetbrains:space-sdk-jvm:83821-beta")
implementation("de.undercouch:gradle-download-task:4.1.2")
}

0
build-helpers/publishing/src/main/kotlin/org/jetbrains/compose/internal/publishing/DownloadFromSpaceTask.kt → ci/build-helpers/buildSrc/src/main/kotlin/org/jetbrains/compose/internal/publishing/DownloadFromSpaceTask.kt

25
build-helpers/publishing/src/main/kotlin/org/jetbrains/compose/internal/publishing/FindModulesInSpaceTask.kt → ci/build-helpers/buildSrc/src/main/kotlin/org/jetbrains/compose/internal/publishing/FindModulesInSpaceTask.kt

@ -13,15 +13,14 @@ import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction import org.gradle.api.tasks.TaskAction
import org.jetbrains.compose.internal.publishing.utils.SpaceApiClient import org.jetbrains.compose.internal.publishing.utils.SpaceApiClient
import org.jetbrains.compose.internal.publishing.utils.SpaceApiClient.PackageInfo
import space.jetbrains.api.runtime.types.PackageRepositoryIdentifier import space.jetbrains.api.runtime.types.PackageRepositoryIdentifier
import space.jetbrains.api.runtime.types.ProjectIdentifier import space.jetbrains.api.runtime.types.ProjectIdentifier
import java.util.regex.Pattern
abstract class FindModulesInSpaceTask : DefaultTask() { abstract class FindModulesInSpaceTask : DefaultTask() {
@get:Input @get:Input
abstract val requestedGroupId: Property<String> abstract val requestedCoordinates: Property<String>
@get:Input
abstract val requestedVersion: Property<String>
@get:Input @get:Input
abstract val spaceInstanceUrl: Property<String> abstract val spaceInstanceUrl: Property<String>
@ -52,11 +51,14 @@ abstract class FindModulesInSpaceTask : DefaultTask() {
val projectId = ProjectIdentifier.Id(spaceProjectId.get()) val projectId = ProjectIdentifier.Id(spaceProjectId.get())
val repoId = PackageRepositoryIdentifier.Id(spaceRepoId.get()) val repoId = PackageRepositoryIdentifier.Id(spaceRepoId.get())
val modules = ArrayList<String>() val modules = ArrayList<String>()
val requestedGroupId = requestedGroupId.get() val requestedCoordinates = requestedCoordinates.get().split(",")
val requestedVersion = requestedVersion.get() requestedCoordinates.forEach { req ->
space.forEachPackageWithVersion(projectId, repoId, requestedVersion) { pkg -> val version = req.substringAfterLast(":") // suppose we don't support wildcards in version
if (pkg.groupId.startsWith(requestedGroupId)) { space.forEachPackageWithVersion(projectId, repoId, version) { pkg ->
modules.add("${pkg.groupId}:${pkg.artifactId}:${pkg.version}") val pkgStr = pkg.toString()
if (pkgStr.matchesWildcard(req)) {
modules.add(pkgStr)
}
} }
} }
@ -66,3 +68,8 @@ abstract class FindModulesInSpaceTask : DefaultTask() {
} }
} }
} }
private fun String.matchesWildcard(pattern: String): Boolean = "\\Q$pattern\\E"
.replace("*", "\\E.*\\Q")
.toRegex()
.matches(this)

0
build-helpers/publishing/src/main/kotlin/org/jetbrains/compose/internal/publishing/FixModulesBeforePublishingTask.kt → ci/build-helpers/buildSrc/src/main/kotlin/org/jetbrains/compose/internal/publishing/FixModulesBeforePublishingTask.kt

10
build-helpers/publishing/src/main/kotlin/org/jetbrains/compose/internal/publishing/MavenCentralProperties.kt → ci/build-helpers/buildSrc/src/main/kotlin/org/jetbrains/compose/internal/publishing/MavenCentralProperties.kt

@ -10,8 +10,14 @@ import org.gradle.api.provider.Provider
@Suppress("unused") // public api @Suppress("unused") // public api
class MavenCentralProperties(private val myProject: Project) { class MavenCentralProperties(private val myProject: Project) {
val version: Provider<String> = val coordinates: Provider<String> =
propertyProvider("maven.central.version") propertyProvider("maven.central.coordinates")
val stage: Provider<String> =
propertyProvider("maven.central.stage")
val description: Provider<String> =
propertyProvider("maven.central.description")
val user: Provider<String> = val user: Provider<String> =
propertyProvider("maven.central.user", envVar = "MAVEN_CENTRAL_USER") propertyProvider("maven.central.user", envVar = "MAVEN_CENTRAL_USER")

0
build-helpers/publishing/src/main/kotlin/org/jetbrains/compose/internal/publishing/ModuleToUpload.kt → ci/build-helpers/buildSrc/src/main/kotlin/org/jetbrains/compose/internal/publishing/ModuleToUpload.kt

8
build-helpers/publishing/src/main/kotlin/org/jetbrains/compose/internal/publishing/UploadToSonatypeTask.kt → ci/build-helpers/buildSrc/src/main/kotlin/org/jetbrains/compose/internal/publishing/UploadToSonatypeTask.kt

@ -28,10 +28,10 @@ abstract class UploadToSonatypeTask : DefaultTask() {
abstract val stagingProfileName: Property<String> abstract val stagingProfileName: Property<String>
@get:Internal @get:Internal
abstract val autoCommitOnSuccess: Property<Boolean> abstract val stagingDescription: Property<String>
@get:Internal @get:Internal
abstract val version: Property<String> abstract val autoCommitOnSuccess: Property<Boolean>
@get:Internal @get:Internal
abstract val modulesToUpload: ListProperty<ModuleToUpload> abstract val modulesToUpload: ListProperty<ModuleToUpload>
@ -59,7 +59,7 @@ abstract class UploadToSonatypeTask : DefaultTask() {
validate(stagingProfile, modules) validate(stagingProfile, modules)
val stagingRepo = sonatype.createStagingRepo( val stagingRepo = sonatype.createStagingRepo(
stagingProfile, "Staging repo for '${stagingProfile.name}' release '${version.get()}'" stagingProfile, stagingDescription.get()
) )
try { try {
for (module in modules) { for (module in modules) {
@ -76,7 +76,7 @@ abstract class UploadToSonatypeTask : DefaultTask() {
private fun validate(stagingProfile: StagingProfile, modules: List<ModuleToUpload>) { private fun validate(stagingProfile: StagingProfile, modules: List<ModuleToUpload>) {
val validationIssues = arrayListOf<Pair<ModuleToUpload, ModuleValidator.Status.Error>>() val validationIssues = arrayListOf<Pair<ModuleToUpload, ModuleValidator.Status.Error>>()
for (module in modules) { for (module in modules) {
val status = ModuleValidator(stagingProfile, module, version.get()).validate() val status = ModuleValidator(stagingProfile, module).validate()
if (status is ModuleValidator.Status.Error) { if (status is ModuleValidator.Status.Error) {
validationIssues.add(module to status) validationIssues.add(module to status)
} }

0
build-helpers/publishing/src/main/kotlin/org/jetbrains/compose/internal/publishing/utils/AbstractRestApiClient.kt → ci/build-helpers/buildSrc/src/main/kotlin/org/jetbrains/compose/internal/publishing/utils/AbstractRestApiClient.kt

0
build-helpers/publishing/src/main/kotlin/org/jetbrains/compose/internal/publishing/utils/Checksum.kt → ci/build-helpers/buildSrc/src/main/kotlin/org/jetbrains/compose/internal/publishing/utils/Checksum.kt

0
build-helpers/publishing/src/main/kotlin/org/jetbrains/compose/internal/publishing/utils/Json.kt → ci/build-helpers/buildSrc/src/main/kotlin/org/jetbrains/compose/internal/publishing/utils/Json.kt

5
build-helpers/publishing/src/main/kotlin/org/jetbrains/compose/internal/publishing/utils/ModuleValidator.kt → ci/build-helpers/buildSrc/src/main/kotlin/org/jetbrains/compose/internal/publishing/utils/ModuleValidator.kt

@ -12,7 +12,6 @@ import java.io.File
internal class ModuleValidator( internal class ModuleValidator(
private val stagingProfile: StagingProfile, private val stagingProfile: StagingProfile,
private val module: ModuleToUpload, private val module: ModuleToUpload,
private val version: String
) { ) {
private val errors = arrayListOf<String>() private val errors = arrayListOf<String>()
private var status: Status? = null private var status: Status? = null
@ -37,10 +36,6 @@ internal class ModuleValidator(
errors.add("Module's group id '${module.groupId}' does not match staging repo '${stagingProfile.name}'") errors.add("Module's group id '${module.groupId}' does not match staging repo '${stagingProfile.name}'")
} }
if (module.version != version) {
errors.add("Unexpected version '${module.version}' (expected: '$version')")
}
val pomFile = artifactFile(extension = "pom") val pomFile = artifactFile(extension = "pom")
val pom = when { val pom = when {
pomFile.exists() -> pomFile.exists() ->

0
build-helpers/publishing/src/main/kotlin/org/jetbrains/compose/internal/publishing/utils/PomDocument.kt → ci/build-helpers/buildSrc/src/main/kotlin/org/jetbrains/compose/internal/publishing/utils/PomDocument.kt

0
build-helpers/publishing/src/main/kotlin/org/jetbrains/compose/internal/publishing/utils/RequestError.kt → ci/build-helpers/buildSrc/src/main/kotlin/org/jetbrains/compose/internal/publishing/utils/RequestError.kt

0
build-helpers/publishing/src/main/kotlin/org/jetbrains/compose/internal/publishing/utils/SonatypeApi.kt → ci/build-helpers/buildSrc/src/main/kotlin/org/jetbrains/compose/internal/publishing/utils/SonatypeApi.kt

0
build-helpers/publishing/src/main/kotlin/org/jetbrains/compose/internal/publishing/utils/SonatypeRestApiClient.kt → ci/build-helpers/buildSrc/src/main/kotlin/org/jetbrains/compose/internal/publishing/utils/SonatypeRestApiClient.kt

4
build-helpers/publishing/src/main/kotlin/org/jetbrains/compose/internal/publishing/utils/SpaceApiClient.kt → ci/build-helpers/buildSrc/src/main/kotlin/org/jetbrains/compose/internal/publishing/utils/SpaceApiClient.kt

@ -21,7 +21,9 @@ internal class SpaceApiClient(
val groupId: String, val groupId: String,
val artifactId: String, val artifactId: String,
val version: String val version: String
) ) {
override fun toString() = "$groupId:$artifactId:$version"
}
fun forEachPackageWithVersion( fun forEachPackageWithVersion(
projectId: ProjectIdentifier, projectId: ProjectIdentifier,

0
build-helpers/publishing/src/main/kotlin/org/jetbrains/compose/internal/publishing/utils/Xml.kt → ci/build-helpers/buildSrc/src/main/kotlin/org/jetbrains/compose/internal/publishing/utils/Xml.kt

0
build-helpers/gradle/wrapper/gradle-wrapper.jar → ci/build-helpers/gradle/wrapper/gradle-wrapper.jar vendored

2
build-helpers/gradle/wrapper/gradle-wrapper.properties → ci/build-helpers/gradle/wrapper/gradle-wrapper.properties vendored

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

0
build-helpers/gradlew → ci/build-helpers/gradlew vendored

0
build-helpers/gradlew.bat → ci/build-helpers/gradlew.bat vendored

0
ci/build-helpers/settings.gradle.kts

2
components/gradle.properties

@ -9,7 +9,7 @@ android.useAndroidX=true
#Versions #Versions
kotlin.version=1.9.24 kotlin.version=1.9.24
agp.version=8.2.2 agp.version=8.2.2
compose.version=1.7.0 compose.version=1.8.0-alpha01
deploy.version=0.1.0-SNAPSHOT deploy.version=0.1.0-SNAPSHOT
#Compose #Compose

BIN
components/resources/demo/shared/src/commonMain/composeResources/font/RobotoFlex-VariableFont.ttf

Binary file not shown.

66
components/resources/demo/shared/src/commonMain/kotlin/org/jetbrains/compose/resources/demo/shared/FontRes.kt

@ -2,20 +2,33 @@ package org.jetbrains.compose.resources.demo.shared
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedCard import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Slider
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontVariation
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import components.resources.demo.shared.generated.resources.Res import components.resources.demo.shared.generated.resources.Res
import components.resources.demo.shared.generated.resources.* import components.resources.demo.shared.generated.resources.RobotoFlex_VariableFont
import components.resources.demo.shared.generated.resources.Workbench_Regular
import components.resources.demo.shared.generated.resources.font_awesome
import org.jetbrains.compose.resources.Font import org.jetbrains.compose.resources.Font
import kotlin.math.roundToInt
@Composable @Composable
fun FontRes(paddingValues: PaddingValues) { fun FontRes(paddingValues: PaddingValues) {
@ -74,5 +87,56 @@ fun FontRes(paddingValues: PaddingValues) {
style = MaterialTheme.typography.headlineLarge, style = MaterialTheme.typography.headlineLarge,
text = "\uf1ba \uf238 \uf21a \uf1bb \uf1b8 \uf09b \uf269 \uf1d0 \uf15a \uf293 \uf1c6" text = "\uf1ba \uf238 \uf21a \uf1bb \uf1b8 \uf09b \uf269 \uf1d0 \uf15a \uf293 \uf1c6"
) )
var variableFontWeight by remember { mutableStateOf(200) }
val variableFont = Font(
resource = Res.font.RobotoFlex_VariableFont,
variationSettings = FontVariation.Settings(FontVariation.weight(variableFontWeight))
)
OutlinedCard(
modifier = Modifier.padding(16.dp).fillMaxWidth(),
shape = RoundedCornerShape(4.dp),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.primaryContainer)
) {
Text(
modifier = Modifier.padding(8.dp),
text = """
Text(
modifier = Modifier.padding(16.dp),
fontFamily = FontFamily(
Font(
Res.font.RobotoFlex_VariableFont,
variationSettings = FontVariation.Settings(
FontVariation.weight($variableFontWeight)
)
),
),
text = "The quick brown fox jumps over the lazy dog"
)
""".trimIndent(),
color = MaterialTheme.colorScheme.onPrimaryContainer,
softWrap = false
)
}
Text(
modifier = Modifier.padding(16.dp),
fontFamily = FontFamily(variableFont),
text = "The quick brown fox jumps over the lazy dog"
)
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(horizontal = 16.dp)
) {
Text(text = "Weight:")
Spacer(modifier = Modifier.size(16.dp))
Slider(
value = variableFontWeight.toFloat(),
onValueChange = { variableFontWeight = it.roundToInt() },
valueRange = 100f..1000f,
steps = 9
)
}
} }
} }

7
components/resources/library/build.gradle.kts

@ -212,10 +212,3 @@ tasks.register<GeneratePluralRuleListsTask>("generatePluralRuleLists") {
outputFile = projectDir.file("src/commonMain/kotlin/org/jetbrains/compose/resources/plural/CLDRPluralRuleLists.kt") outputFile = projectDir.file("src/commonMain/kotlin/org/jetbrains/compose/resources/plural/CLDRPluralRuleLists.kt")
samplesOutputFile = projectDir.file("src/commonTest/kotlin/org/jetbrains/compose/resources/CLDRPluralRuleLists.test.kt") samplesOutputFile = projectDir.file("src/commonTest/kotlin/org/jetbrains/compose/resources/CLDRPluralRuleLists.test.kt")
} }
afterEvaluate {
// TODO(o.k.): remove this after we refactor jsAndWasmMain source set in skiko to get rid of broken "common" js-interop
tasks.configureEach {
if (name == "compileWebMainKotlinMetadata") enabled = false
}
}

17
components/resources/library/src/androidMain/kotlin/org/jetbrains/compose/resources/FontResources.android.kt

@ -5,6 +5,10 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.* import androidx.compose.ui.text.font.*
@Deprecated(
message = "Use the new Font function with variationSettings instead.",
level = DeprecationLevel.HIDDEN
)
@Composable @Composable
actual fun Font(resource: FontResource, weight: FontWeight, style: FontStyle): Font { actual fun Font(resource: FontResource, weight: FontWeight, style: FontStyle): Font {
val environment = LocalComposeEnvironment.current.rememberEnvironment() val environment = LocalComposeEnvironment.current.rememberEnvironment()
@ -12,3 +16,16 @@ actual fun Font(resource: FontResource, weight: FontWeight, style: FontStyle): F
val assets = LocalContext.current.assets val assets = LocalContext.current.assets
return Font(path, assets, weight, style) return Font(path, assets, weight, style)
} }
@Composable
actual fun Font(
resource: FontResource,
weight: FontWeight,
style: FontStyle,
variationSettings: FontVariation.Settings,
): Font {
val environment = LocalComposeEnvironment.current.rememberEnvironment()
val path = remember(environment, resource) { resource.getResourceItemByEnvironment(environment).path }
val assets = LocalContext.current.assets
return Font(path, assets, weight, style, variationSettings)
}

17
components/resources/library/src/blockingMain/kotlin/org/jetbrains/compose/resources/ResourceState.blocking.kt

@ -47,3 +47,20 @@ internal actual fun <T> rememberResourceState(
) )
} }
} }
@Composable
internal actual fun <T> rememberResourceState(
key1: Any,
key2: Any,
key3: Any,
key4: Any,
getDefault: () -> T,
block: suspend (ResourceEnvironment) -> T
): State<T> {
val environment = LocalComposeEnvironment.current.rememberEnvironment()
return remember(key1, key2, key3, key4, environment) {
mutableStateOf(
runBlocking { block(environment) }
)
}
}

26
components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/FontResources.kt

@ -15,7 +15,7 @@ import androidx.compose.ui.text.font.*
*/ */
@Immutable @Immutable
class FontResource class FontResource
@InternalResourceApi constructor(id: String, items: Set<ResourceItem>): Resource(id, items) @InternalResourceApi constructor(id: String, items: Set<ResourceItem>) : Resource(id, items)
/** /**
* Creates a font using the specified font resource, weight, and style. * Creates a font using the specified font resource, weight, and style.
@ -28,6 +28,10 @@ class FontResource
* *
* @throws NotFoundException if the specified resource ID is not found. * @throws NotFoundException if the specified resource ID is not found.
*/ */
@Deprecated(
message = "Use the new Font function with variationSettings instead.",
level = DeprecationLevel.HIDDEN
)
@Composable @Composable
expect fun Font( expect fun Font(
resource: FontResource, resource: FontResource,
@ -35,6 +39,26 @@ expect fun Font(
style: FontStyle = FontStyle.Normal style: FontStyle = FontStyle.Normal
): Font ): Font
/**
* Creates a font using the specified font resource, weight, and style.
*
* @param resource The font resource to be used.
* @param weight The weight of the font. Default value is [FontWeight.Normal].
* @param style The style of the font. Default value is [FontStyle.Normal].
* @param variationSettings Custom variation settings for the font, with a default value derived from the specified [weight] and [style].
*
* @return The created [Font] object.
*
* @throws NotFoundException if the specified resource ID is not found.
*/
@Composable
expect fun Font(
resource: FontResource,
weight: FontWeight = FontWeight.Normal,
style: FontStyle = FontStyle.Normal,
variationSettings: FontVariation.Settings = FontVariation.Settings(weight, style)
): Font
/** /**
* Retrieves the byte array of the font resource. * Retrieves the byte array of the font resource.
* *

16
components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ResourceState.kt

@ -41,3 +41,19 @@ internal expect fun <T> rememberResourceState(
getDefault: () -> T, getDefault: () -> T,
block: suspend (ResourceEnvironment) -> T block: suspend (ResourceEnvironment) -> T
): State<T> ): State<T>
/**
* This is a platform-specific function that calculates and remembers a state.
* For all platforms except a JS it is a blocking function.
* On the JS platform it loads the state asynchronously and uses `getDefault` as an initial state value.
*/
@Composable
internal expect fun <T> rememberResourceState(
key1: Any,
key2: Any,
key3: Any,
key4: Any,
getDefault: () -> T,
block: suspend (ResourceEnvironment) -> T
): State<T>

2
components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/StringResourcesUtils.kt

@ -4,7 +4,7 @@ import org.jetbrains.compose.resources.plural.PluralCategory
import kotlin.io.encoding.Base64 import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi import kotlin.io.encoding.ExperimentalEncodingApi
private val SimpleStringFormatRegex = Regex("""%(\d)\$[ds]""") private val SimpleStringFormatRegex = Regex("""%(\d+)\$[ds]""")
internal fun String.replaceWithArgs(args: List<String>) = SimpleStringFormatRegex.replace(this) { matchResult -> internal fun String.replaceWithArgs(args: List<String>) = SimpleStringFormatRegex.replace(this) { matchResult ->
args[matchResult.groupValues[1].toInt() - 1] args[matchResult.groupValues[1].toInt() - 1]
} }

181
components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/StringFormatTest.kt

@ -0,0 +1,181 @@
package org.jetbrains.compose.resources
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class StringFormatTest {
@Test
fun `replaceWithArgs replaces placeholders with corresponding arguments`() {
val template = "Hello %1\$s, you have %2\$d new messages!"
val args = listOf("Alice", "5")
val result = template.replaceWithArgs(args)
assertEquals("Hello Alice, you have 5 new messages!", result)
}
@Test
fun `replaceWithArgs works with multiple placeholders referring to the same argument`() {
val template = "%1\$s and %1\$s are best friends!"
val args = listOf("Alice")
val result = template.replaceWithArgs(args)
assertEquals("Alice and Alice are best friends!", result)
}
@Test
fun `replaceWithArgs works when placeholders are out of order`() {
val template = "Order: %2\$s comes after %1\$s"
val args = listOf("Alice", "Bob")
val result = template.replaceWithArgs(args)
assertEquals("Order: Bob comes after Alice", result)
}
@Test
fun `replaceWithArgs works when there are no placeholders`() {
val template = "No placeholders here!"
val args = emptyList<String>()
val result = template.replaceWithArgs(args)
assertEquals("No placeholders here!", result)
}
@Test
fun `replaceWithArgs throws exception when placeholders index is out of bounds`() {
val template = "Hello %1\$s, %2\$s!"
val args = listOf("Alice")
assertFailsWith<IndexOutOfBoundsException> {
template.replaceWithArgs(args)
}
}
@Test
fun `replaceWithArgs handles empty string template`() {
val template = ""
val args = listOf("Alice", "5")
val result = template.replaceWithArgs(args)
assertEquals("", result)
}
@Test
fun `replaceWithArgs handles templates with no matching args`() {
val template = "Hello %1\$s, you have %3\$s messages"
val args = listOf("Alice")
assertFailsWith<IndexOutOfBoundsException> {
template.replaceWithArgs(args)
}
}
@Test
fun `replaceWithArgs replaces multiple placeholders of the same index`() {
val template = "Repeat: %1\$s, %1\$s, and again %1\$s!"
val args = listOf("Echo")
val result = template.replaceWithArgs(args)
assertEquals("Repeat: Echo, Echo, and again Echo!", result)
}
@Test
fun `replaceWithArgs ensures %d and %s placeholders behave identically`() {
val template = "%1\$d, %1\$s, %2\$d, %2\$s"
val args = listOf("42", "hello")
val result = template.replaceWithArgs(args)
assertEquals("42, 42, hello, hello", result)
}
@Test
fun `replaceWithArgs handles 15 arguments correctly`() {
val template = "%1\$s, %2\$s, %3\$s, %4\$s, %5\$s, %6\$s, %7\$s, %8\$s, %9\$s, %10\$s, %11\$s, %12\$s, %13\$s, %14\$s, %15\$s!"
val args = listOf(
"arg1", "arg2", "arg3", "arg4", "arg5",
"arg6", "arg7", "arg8", "arg9", "arg10",
"arg11", "arg12", "arg13", "arg14", "arg15"
)
val result = template.replaceWithArgs(args)
assertEquals(
"arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15!",
result
)
}
@Test
fun `replaceWithArgs throws exception for template with missing argument index`() {
val template = "Hello %${'$'}s, how are you?"
val args = listOf("Alice")
val result = template.replaceWithArgs(args)
// Since the template doesn't properly specify a valid index (e.g., %1$s), it will replace nothing.
assertEquals("Hello %${'$'}s, how are you?", result)
}
@Test
fun `replaceWithArgs does not replace invalid placeholders`() {
val template = "Hello %1\$x, how are you?"
val args = listOf("Alice")
val result = template.replaceWithArgs(args)
// %1$x is not a valid placeholder, so the template should remain unchanged
assertEquals("Hello %1\$x, how are you?", result)
}
@Test
fun `replaceWithArgs throws exception for missing arguments`() {
val template = "Hello %1\$s, you have %2\$d messages!"
val args = listOf("Alice")
// An exception should be thrown because the second argument (%2$d) is missing
assertFailsWith<IndexOutOfBoundsException> {
template.replaceWithArgs(args)
}
}
@Test
fun `replaceWithArgs throws exception for unmatched placeholders`() {
val template = "Hello %1\$s, your rank is %3\$s"
val args = listOf("Alice", "1")
// The template has a %3$s placeholder, but there is no third argument
assertFailsWith<IndexOutOfBoundsException> {
template.replaceWithArgs(args)
}
}
@Test
fun `replaceWithArgs handles templates with invalid format`() {
val template = "This is %1\$"
val args = listOf("test")
val result = template.replaceWithArgs(args)
// The incomplete placeholder %1$ will not be replaced
assertEquals("This is %1\$", result)
}
@Test
fun `replaceWithArgs ignores extra arguments`() {
val template = "Hello %1\$s!"
val args = listOf("Alice", "ExtraData1", "ExtraData2")
val result = template.replaceWithArgs(args)
// Only the first argument should be used, ignoring the rest
assertEquals("Hello Alice!", result)
}
}

28
components/resources/library/src/macosMain/kotlin/org/jetbrains/compose/resources/ResourceReader.macos.kt

@ -39,32 +39,12 @@ internal actual fun getPlatformResourceReader(): ResourceReader = object : Resou
private fun getPathOnDisk(path: String): String { private fun getPathOnDisk(path: String): String {
val fm = NSFileManager.defaultManager() val fm = NSFileManager.defaultManager()
val currentDirectoryPath = fm.currentDirectoryPath val currentDirectoryPath = fm.currentDirectoryPath
val pathFix = getPathWithoutPackage(path)
return listOf( return listOf(
// Framework binary
// todo: support fallback path at bundle root?
NSBundle.mainBundle.resourcePath + "/compose-resources/" + path,
// Executable binary
//todo in future bundle resources with app and use all sourceSets (skikoMain, nativeMain) //todo in future bundle resources with app and use all sourceSets (skikoMain, nativeMain)
"$currentDirectoryPath/src/macosMain/composeResources/$pathFix", "$currentDirectoryPath/src/macosMain/composeResources/$path",
"$currentDirectoryPath/src/macosTest/composeResources/$pathFix", "$currentDirectoryPath/src/macosTest/composeResources/$path",
"$currentDirectoryPath/src/commonMain/composeResources/$pathFix", "$currentDirectoryPath/src/commonMain/composeResources/$path",
"$currentDirectoryPath/src/commonTest/composeResources/$pathFix" "$currentDirectoryPath/src/commonTest/composeResources/$path"
).firstOrNull { p -> fm.fileExistsAtPath(p) } ?: throw MissingResourceException(path) ).firstOrNull { p -> fm.fileExistsAtPath(p) } ?: throw MissingResourceException(path)
} }
private fun getPathWithoutPackage(path: String): String {
// At the moment resources are not bundled when running a macOS executable binary.
// As a workaround, load the resources from the actual path on disk. So the
// "composeResources/PACKAGE/" prefix must be removed. For example:
// "composeResources/chat_mpp.shared.generated.resources/drawable/background.jpg"
// Will be transformed into:
// "drawable/background.jpg"
// In the future when resources are bundled when running macOS executable binary this
// workaround is no longer needed.
require(path.startsWith("composeResources/")) { "Invalid path: $path" }
return path
.substringAfter("composeResources/") // remove "composeResources/" part
.substringAfter("/") // remove PACKAGE path
}
} }

32
components/resources/library/src/skikoMain/kotlin/org/jetbrains/compose/resources/FontResources.skiko.kt

@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.text.font.* import androidx.compose.ui.text.font.*
import androidx.compose.ui.text.platform.Font import androidx.compose.ui.text.platform.Font
import androidx.compose.ui.unit.Density
import kotlin.io.encoding.Base64 import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi import kotlin.io.encoding.ExperimentalEncodingApi
@ -33,6 +34,10 @@ private val fontCache = AsyncCache<String, Font>()
internal val Font.isEmptyPlaceholder: Boolean internal val Font.isEmptyPlaceholder: Boolean
get() = this == defaultEmptyFont get() = this == defaultEmptyFont
@Deprecated(
message = "Use the new Font function with variationSettings instead.",
level = DeprecationLevel.HIDDEN
)
@Composable @Composable
actual fun Font(resource: FontResource, weight: FontWeight, style: FontStyle): Font { actual fun Font(resource: FontResource, weight: FontWeight, style: FontStyle): Font {
val resourceReader = LocalResourceReader.currentOrPreview val resourceReader = LocalResourceReader.currentOrPreview
@ -46,3 +51,30 @@ actual fun Font(resource: FontResource, weight: FontWeight, style: FontStyle): F
} }
return fontFile return fontFile
} }
@Composable
actual fun Font(
resource: FontResource,
weight: FontWeight,
style: FontStyle,
variationSettings: FontVariation.Settings,
): Font {
val resourceReader = LocalResourceReader.currentOrPreview
val fontFile by rememberResourceState(resource, weight, style, variationSettings, { defaultEmptyFont }) { env ->
val path = resource.getResourceItemByEnvironment(env).path
val key = "$path:$weight:$style:${variationSettings.getCacheKey()}"
fontCache.getOrLoad(key) {
val fontBytes = resourceReader.read(path)
Font(key, fontBytes, weight, style, variationSettings)
}
}
return fontFile
}
internal fun FontVariation.Settings.getCacheKey(): String {
val defaultDensity = Density(1f)
return settings
.map { "${it::class.simpleName}(${it.axisName},${it.toVariationValue(defaultDensity)})" }
.sorted()
.joinToString(",")
}

49
components/resources/library/src/skikoTest/kotlin/org/jetbrains/compose/resources/VariationFontCacheTest.kt

@ -0,0 +1,49 @@
package org.jetbrains.compose.resources
import androidx.compose.ui.text.font.FontVariation
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class VariationFontCacheTest {
@Test
fun `getCacheKey should return an empty string for an empty settings list`() {
val settings = FontVariation.Settings()
val cacheKey = settings.getCacheKey()
assertEquals("", cacheKey, "Cache key for empty settings list should be an empty string")
}
@Test
fun `getCacheKey should return a correct key for a single setting`() {
val setting = FontVariation.Setting("wght", 700f)
val settings = FontVariation.Settings(setting)
val cacheKey = settings.getCacheKey()
assertEquals("SettingFloat(wght,700.0)", cacheKey, "Cache key for a single setting is incorrect")
}
@Test
fun `getCacheKey should correctly sort settings by class name and axis name`() {
val setting1 = FontVariation.Setting("wght", 400f)
val setting2 = FontVariation.Setting("ital", 1f)
val settings = FontVariation.Settings(setting1, setting2)
val cacheKey = settings.getCacheKey()
assertEquals(
"SettingFloat(ital,1.0),SettingFloat(wght,400.0)",
cacheKey,
"Cache key should sort settings by class name and axis name"
)
}
@Test
fun `getCacheKey should throw an exception when there are duplicate settings`() {
val setting1 = FontVariation.Setting("wght", 400f)
val setting2 = FontVariation.Setting("wght", 700f)
assertFailsWith<IllegalArgumentException>(
"'axis' must be unique"
) {
FontVariation.Settings(setting1, setting2)
}
}
}

9
components/resources/library/src/webMain/kotlin/org/jetbrains/compose/resources/Resource.web.kt

@ -5,6 +5,7 @@ import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontVariation
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
/** /**
@ -90,6 +91,7 @@ internal fun getResourceUrl(windowOrigin: String, windowPathname: String, resour
* @param resource The font resource to be used. * @param resource The font resource to be used.
* @param weight The weight of the font. Default value is [FontWeight.Normal]. * @param weight The weight of the font. Default value is [FontWeight.Normal].
* @param style The style of the font. Default value is [FontStyle.Normal]. * @param style The style of the font. Default value is [FontStyle.Normal].
* @param variationSettings Custom variation settings for the font, with a default value derived from the specified [weight] and [style].
* @return A [State]<[Font]?> object that holds the loaded [Font] when available, * @return A [State]<[Font]?> object that holds the loaded [Font] when available,
* or `null` if the font is not yet ready. * or `null` if the font is not yet ready.
*/ */
@ -98,10 +100,11 @@ internal fun getResourceUrl(windowOrigin: String, windowPathname: String, resour
fun preloadFont( fun preloadFont(
resource: FontResource, resource: FontResource,
weight: FontWeight = FontWeight.Normal, weight: FontWeight = FontWeight.Normal,
style: FontStyle = FontStyle.Normal style: FontStyle = FontStyle.Normal,
variationSettings: FontVariation.Settings = FontVariation.Settings(weight, style),
): State<Font?> { ): State<Font?> {
val resState = remember(resource, weight, style) { mutableStateOf<Font?>(null) }.apply { val resState = remember(resource, weight, style, variationSettings) { mutableStateOf<Font?>(null) }.apply {
value = Font(resource, weight, style).takeIf { !it.isEmptyPlaceholder } value = Font(resource, weight, style, variationSettings).takeIf { !it.isEmptyPlaceholder }
} }
return resState return resState
} }

20
components/resources/library/src/webMain/kotlin/org/jetbrains/compose/resources/ResourceState.web.kt

@ -61,3 +61,23 @@ internal actual fun <T> rememberResourceState(
mutableState mutableState
} }
} }
@Composable
internal actual fun <T> rememberResourceState(
key1: Any,
key2: Any,
key3: Any,
key4: Any,
getDefault: () -> T,
block: suspend (ResourceEnvironment) -> T
): State<T> {
val environment = LocalComposeEnvironment.current.rememberEnvironment()
val scope = rememberCoroutineScope()
return remember(key1, key2, key3, key4, environment) {
val mutableState = mutableStateOf(getDefault())
scope.launch(start = CoroutineStart.UNDISPATCHED) {
mutableState.value = block(environment)
}
mutableState
}
}

7
components/ui-tooling-preview/library/build.gradle.kts

@ -52,10 +52,3 @@ configureMavenPublication(
name = "Experimental Compose Multiplatform tooling library API. This library provides the API required to declare " + name = "Experimental Compose Multiplatform tooling library API. This library provides the API required to declare " +
"@Preview composables in user apps." "@Preview composables in user apps."
) )
afterEvaluate {
// TODO(o.k.): remove this after we refactor jsAndWasmMain source set in skiko to get rid of broken "common" js-interop
tasks.configureEach {
if (name == "compileWebMainKotlinMetadata") enabled = false
}
}

15
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/IosResources.kt

@ -33,7 +33,7 @@ internal fun Project.configureSyncIosComposeResources(
} }
kotlinExtension.targets.withType(KotlinNativeTarget::class.java).all { nativeTarget -> kotlinExtension.targets.withType(KotlinNativeTarget::class.java).all { nativeTarget ->
if (nativeTarget.isIosOrMacTarget()) { if (nativeTarget.isIosTarget()) {
nativeTarget.binaries.withType(Framework::class.java).all { iosFramework -> nativeTarget.binaries.withType(Framework::class.java).all { iosFramework ->
val frameworkClassifier = iosFramework.getClassifier() val frameworkClassifier = iosFramework.getClassifier()
val checkNoSandboxTask = tasks.registerOrConfigure<CheckCanAccessComposeResourcesDirectory>( val checkNoSandboxTask = tasks.registerOrConfigure<CheckCanAccessComposeResourcesDirectory>(
@ -116,7 +116,6 @@ private fun Framework.getClassifier(): String {
} }
internal fun Framework.getSyncResourcesTaskName() = "sync${getClassifier()}ComposeResourcesForIos" internal fun Framework.getSyncResourcesTaskName() = "sync${getClassifier()}ComposeResourcesForIos"
private fun Framework.isCocoapodsFramework() = name.startsWith("pod") private fun Framework.isCocoapodsFramework() = name.startsWith("pod")
private fun Framework.getFinalResourcesDir(): Provider<Directory> { private fun Framework.getFinalResourcesDir(): Provider<Directory> {
@ -126,9 +125,9 @@ private fun Framework.getFinalResourcesDir(): Provider<Directory> {
} else { } else {
providers.environmentVariable("BUILT_PRODUCTS_DIR") providers.environmentVariable("BUILT_PRODUCTS_DIR")
.zip( .zip(
providers.environmentVariable("UNLOCALIZED_RESOURCES_FOLDER_PATH") providers.environmentVariable("CONTENTS_FOLDER_PATH")
) { builtProductsDir, unlocalizedResourcesFolderPath -> ) { builtProductsDir, contentsFolderPath ->
File("$builtProductsDir/$unlocalizedResourcesFolderPath/$IOS_COMPOSE_RESOURCES_ROOT_DIR").canonicalPath File("$builtProductsDir/$contentsFolderPath/$IOS_COMPOSE_RESOURCES_ROOT_DIR").canonicalPath
} }
.flatMap { .flatMap {
project.objects.directoryProperty().apply { set(File(it)) } project.objects.directoryProperty().apply { set(File(it)) }
@ -144,9 +143,3 @@ private fun KotlinNativeTarget.isIosDeviceTarget(): Boolean =
private fun KotlinNativeTarget.isIosTarget(): Boolean = private fun KotlinNativeTarget.isIosTarget(): Boolean =
isIosSimulatorTarget() || isIosDeviceTarget() isIosSimulatorTarget() || isIosDeviceTarget()
private fun KotlinNativeTarget.isMacTarget(): Boolean =
konanTarget === KonanTarget.MACOS_X64 || konanTarget === KonanTarget.MACOS_ARM64
private fun KotlinNativeTarget.isIosOrMacTarget(): Boolean =
isIosTarget() || isMacTarget()

12
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/IosResourcesTasks.kt

@ -118,17 +118,7 @@ private fun getRequestedKonanTargetsByXcode(platform: String, archs: List<String
}) })
} }
platform.startsWith("macosx") -> { else -> error("Unknown iOS platform: '$platform'")
targets.addAll(archs.map { arch ->
when (arch) {
"arm64" -> KonanTarget.MACOS_ARM64
"x86_64" -> KonanTarget.MACOS_X64
else -> error("Unknown macOS arch: '$arch'")
}
})
}
else -> error("Unknown Apple platform: '$platform'")
} }
return targets.toList() return targets.toList()

122
gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/ResourcesTest.kt

@ -280,13 +280,6 @@ class ResourcesTest : GradlePluginTestBase() {
libpath("iossimulatorarm64", "-kotlin_resources.kotlin_resources.zip") libpath("iossimulatorarm64", "-kotlin_resources.kotlin_resources.zip")
) )
checkResourcesZip(iossimulatorarm64ResZip, resourcesFiles, false) checkResourcesZip(iossimulatorarm64ResZip, resourcesFiles, false)
val macosx64ResZip =
file(libpath("macosx64", "-kotlin_resources.kotlin_resources.zip"))
checkResourcesZip(macosx64ResZip, resourcesFiles, false)
val macosarm64ResZip =
file(libpath("macosarm64", "-kotlin_resources.kotlin_resources.zip"))
checkResourcesZip(macosarm64ResZip, resourcesFiles, false)
} }
val jsResZip = file(libpath("js", "-kotlin_resources.kotlin_resources.zip")) val jsResZip = file(libpath("js", "-kotlin_resources.kotlin_resources.zip"))
checkResourcesZip(jsResZip, resourcesFiles, false) checkResourcesZip(jsResZip, resourcesFiles, false)
@ -307,13 +300,6 @@ class ResourcesTest : GradlePluginTestBase() {
":appModule:iosSimulatorArm64Test" ":appModule:iosSimulatorArm64Test"
} }
gradle(iosTask) gradle(iosTask)
val macosTask = if (currentArch == Arch.X64) {
":appModule:macosX64Test"
} else {
":appModule:macosArm64Test"
}
gradle(macosTask)
} }
file("featureModule/src/commonMain/kotlin/me/sample/app/Feature.kt").modify { content -> file("featureModule/src/commonMain/kotlin/me/sample/app/Feature.kt").modify { content ->
@ -701,95 +687,6 @@ class ResourcesTest : GradlePluginTestBase() {
} }
} }
@Test
fun macosResources() {
Assumptions.assumeTrue(currentOS == OS.MacOS)
val macosEnv = mapOf(
"PLATFORM_NAME" to "macosx",
"ARCHS" to "arm64",
"CONFIGURATION" to "Debug",
)
val testEnv = defaultTestEnvironment.copy(
additionalEnvVars = macosEnv
)
with(TestProject("misc/macosResources", testEnv)) {
gradle(":podspec", "-Pkotlin.native.cocoapods.generate.wrapper=true").checks {
assertEqualTextFiles(
file("macosResources.podspec"),
file("expected/macosResources.podspec")
)
file("build/compose/cocoapods/compose-resources").checkExists()
}
gradle(
":syncFramework",
"-Pkotlin.native.cocoapods.platform=${macosEnv["PLATFORM_NAME"]}",
"-Pkotlin.native.cocoapods.archs=${macosEnv["ARCHS"]}",
"-Pkotlin.native.cocoapods.configuration=${macosEnv["CONFIGURATION"]}",
"--dry-run"
).checks {
check.taskSkipped(":generateComposeResClass")
check.taskSkipped(":convertXmlValueResourcesForCommonMain")
check.taskSkipped(":copyNonXmlValueResourcesForCommonMain")
check.taskSkipped(":prepareComposeResourcesTaskForCommonMain")
check.taskSkipped(":generateResourceAccessorsForCommonMain")
check.taskSkipped(":convertXmlValueResourcesForNativeMain")
check.taskSkipped(":copyNonXmlValueResourcesForNativeMain")
check.taskSkipped(":prepareComposeResourcesTaskForNativeMain")
check.taskSkipped(":generateResourceAccessorsForNativeMain")
check.taskSkipped(":convertXmlValueResourcesForAppleMain")
check.taskSkipped(":copyNonXmlValueResourcesForAppleMain")
check.taskSkipped(":prepareComposeResourcesTaskForAppleMain")
check.taskSkipped(":generateResourceAccessorsForAppleMain")
check.taskSkipped(":convertXmlValueResourcesForMacosMain")
check.taskSkipped(":copyNonXmlValueResourcesForMacosMain")
check.taskSkipped(":prepareComposeResourcesTaskForMacosMain")
check.taskSkipped(":generateResourceAccessorsForMacosMain")
check.taskSkipped(":convertXmlValueResourcesForMacosX64Main")
check.taskSkipped(":copyNonXmlValueResourcesForMacosX64Main")
check.taskSkipped(":prepareComposeResourcesTaskForMacosX64Main")
check.taskSkipped(":generateResourceAccessorsForMacosX64Main")
check.taskSkipped(":syncPodComposeResourcesForIos")
}
gradle(":syncPodComposeResourcesForIos").checks {
check.taskNoSource(":convertXmlValueResourcesForCommonMain")
check.taskSuccessful(":copyNonXmlValueResourcesForCommonMain")
check.taskSuccessful(":prepareComposeResourcesTaskForCommonMain")
check.taskSkipped(":generateResourceAccessorsForCommonMain")
check.taskNoSource(":convertXmlValueResourcesForNativeMain")
check.taskNoSource(":copyNonXmlValueResourcesForNativeMain")
check.taskNoSource(":prepareComposeResourcesTaskForNativeMain")
check.taskSkipped(":generateResourceAccessorsForNativeMain")
check.taskNoSource(":convertXmlValueResourcesForAppleMain")
check.taskNoSource(":copyNonXmlValueResourcesForAppleMain")
check.taskNoSource(":prepareComposeResourcesTaskForAppleMain")
check.taskSkipped(":generateResourceAccessorsForAppleMain")
check.taskNoSource(":convertXmlValueResourcesForMacosMain")
check.taskSuccessful(":copyNonXmlValueResourcesForMacosMain")
check.taskSuccessful(":prepareComposeResourcesTaskForMacosMain")
check.taskSkipped(":generateResourceAccessorsForMacosMain")
check.taskNoSource(":convertXmlValueResourcesForMacosX64Main")
check.taskNoSource(":copyNonXmlValueResourcesForMacosX64Main")
check.taskNoSource(":prepareComposeResourcesTaskForMacosX64Main")
check.taskSkipped(":generateResourceAccessorsForMacosX64Main")
file("build/compose/cocoapods/compose-resources/composeResources/macosresources.generated.resources/drawable/compose-multiplatform.xml").checkExists()
file("build/compose/cocoapods/compose-resources/composeResources/macosresources.generated.resources/drawable/icon.xml").checkExists()
}
}
}
@Test @Test
fun iosTestResources() { fun iosTestResources() {
Assumptions.assumeTrue(currentOS == OS.MacOS) Assumptions.assumeTrue(currentOS == OS.MacOS)
@ -805,21 +702,6 @@ class ResourcesTest : GradlePluginTestBase() {
} }
} }
@Test
fun macosTestResources() {
Assumptions.assumeTrue(currentOS == OS.MacOS)
with(testProject("misc/macosResources")) {
gradle(":linkDebugTestMacosX64", "--dry-run").checks {
check.taskSkipped(":copyTestComposeResourcesForMacosX64")
check.taskSkipped(":linkDebugTestMacosX64")
}
gradle(":copyTestComposeResourcesForMacosX64").checks {
file("build/bin/macosX64/debugTest/compose-resources/composeResources/macosresources.generated.resources/drawable/compose-multiplatform.xml").checkExists()
file("build/bin/macosX64/debugTest/compose-resources/composeResources/macosresources.generated.resources/drawable/icon.xml").checkExists()
}
}
}
@Test @Test
fun checkTestResources() { fun checkTestResources() {
with(testProject("misc/testResources")) { with(testProject("misc/testResources")) {
@ -832,10 +714,6 @@ class ResourcesTest : GradlePluginTestBase() {
check.logContains("Configure test resources for 'iosArm64' target") check.logContains("Configure test resources for 'iosArm64' target")
check.logContains("Configure main resources for 'iosSimulatorArm64' target") check.logContains("Configure main resources for 'iosSimulatorArm64' target")
check.logContains("Configure test resources for 'iosSimulatorArm64' target") check.logContains("Configure test resources for 'iosSimulatorArm64' target")
check.logContains("Configure main resources for 'macosX64' target")
check.logContains("Configure test resources for 'macosX64' target")
check.logContains("Configure main resources for 'macosArm64' target")
check.logContains("Configure test resources for 'macosArm64' target")
check.taskSuccessful(":desktopTest") check.taskSuccessful(":desktopTest")
} }

3
gradle-plugins/compose/src/test/test-projects/misc/commonResources/build.gradle.kts

@ -64,6 +64,9 @@ android {
sourceCompatibility = JavaVersion.VERSION_11 sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11
} }
lint {
checkReleaseBuilds = false
}
} }
abstract class GenerateAndroidRes : DefaultTask() { abstract class GenerateAndroidRes : DefaultTask() {

2
gradle-plugins/compose/src/test/test-projects/misc/kmpResourcePublication/appModule/build.gradle.kts

@ -13,8 +13,6 @@ kotlin {
iosX64() iosX64()
iosArm64() iosArm64()
iosSimulatorArm64() iosSimulatorArm64()
macosX64()
macosArm64()
js { browser() } js { browser() }
wasmJs { browser() } wasmJs { browser() }

3
gradle-plugins/compose/src/test/test-projects/misc/kmpResourcePublication/appModule/src/macosMain/composeResources/values/macos_strings.xml

@ -1,3 +0,0 @@
<resources>
<string name="macOS_str">macOS string</string>
</resources>

10
gradle-plugins/compose/src/test/test-projects/misc/kmpResourcePublication/appModule/src/macosMain/kotlin/me/sample/app/App.macos.kt

@ -1,10 +0,0 @@
package me.sample.app
import androidx.compose.runtime.Composable
import kmpresourcepublication.appmodule.generated.resources.Res
import kmpresourcepublication.appmodule.generated.resources.macOS_str
import org.jetbrains.compose.resources.stringResource
@Composable
actual fun getPlatformSpecificString(): String =
stringResource(Res.string.macOS_str)

2
gradle-plugins/compose/src/test/test-projects/misc/kmpResourcePublication/cmplib/build.gradle.kts

@ -22,8 +22,6 @@ kotlin {
iosX64() iosX64()
iosArm64() iosArm64()
iosSimulatorArm64() iosSimulatorArm64()
macosX64()
macosArm64()
js { browser() } js { browser() }
wasmJs { browser() } wasmJs { browser() }

2
gradle-plugins/compose/src/test/test-projects/misc/kmpResourcePublication/featureModule/build.gradle.kts

@ -11,8 +11,6 @@ kotlin {
iosX64() iosX64()
iosArm64() iosArm64()
iosSimulatorArm64() iosSimulatorArm64()
macosX64()
macosArm64()
js { browser() } js { browser() }
wasmJs { browser() } wasmJs { browser() }

1
gradle-plugins/compose/src/test/test-projects/misc/kmpResourcePublication/gradle.properties

@ -5,4 +5,3 @@ android.useAndroidX=true
org.jetbrains.compose.experimental.uikit.enabled=true org.jetbrains.compose.experimental.uikit.enabled=true
org.jetbrains.compose.experimental.jscanvas.enabled=true org.jetbrains.compose.experimental.jscanvas.enabled=true
org.jetbrains.compose.experimental.wasm.enabled=true org.jetbrains.compose.experimental.wasm.enabled=true
org.jetbrains.compose.experimental.macos.enabled=true

32
gradle-plugins/compose/src/test/test-projects/misc/macosResources/build.gradle.kts

@ -1,32 +0,0 @@
plugins {
kotlin("multiplatform")
kotlin("plugin.compose")
kotlin("native.cocoapods")
id("org.jetbrains.compose")
}
kotlin {
cocoapods {
version = "1.0"
summary = "Some description for a Kotlin/Native module"
homepage = "Link to a Kotlin/Native module homepage"
pod("Base64", "1.1.2")
framework {
baseName = "shared"
isStatic = true
}
}
macosX64()
macosArm64()
sourceSets {
commonMain {
dependencies {
implementation(compose.runtime)
implementation(compose.material)
implementation(compose.components.resources)
}
}
}
}

54
gradle-plugins/compose/src/test/test-projects/misc/macosResources/expected/macosResources.podspec

@ -1,54 +0,0 @@
Pod::Spec.new do |spec|
spec.name = 'macosResources'
spec.version = '1.0'
spec.homepage = 'Link to a Kotlin/Native module homepage'
spec.source = { :http=> ''}
spec.authors = ''
spec.license = ''
spec.summary = 'Some description for a Kotlin/Native module'
spec.vendored_frameworks = 'build/cocoapods/framework/shared.framework'
spec.libraries = 'c++'
spec.dependency 'Base64', '1.1.2'
if !Dir.exist?('build/cocoapods/framework/shared.framework') || Dir.empty?('build/cocoapods/framework/shared.framework')
raise "
Kotlin framework 'shared' doesn't exist yet, so a proper Xcode project can't be generated.
'pod install' should be executed after running ':generateDummyFramework' Gradle task:
./gradlew :generateDummyFramework
Alternatively, proper pod installation is performed during Gradle sync in the IDE (if Podfile location is set)"
end
spec.xcconfig = {
'ENABLE_USER_SCRIPT_SANDBOXING' => 'NO',
}
spec.pod_target_xcconfig = {
'KOTLIN_PROJECT_PATH' => '',
'PRODUCT_MODULE_NAME' => 'shared',
}
spec.script_phases = [
{
:name => 'Build macosResources',
:execution_position => :before_compile,
:shell_path => '/bin/sh',
:script => <<-SCRIPT
if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then
echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\""
exit 0
fi
set -ev
REPO_ROOT="$PODS_TARGET_SRCROOT"
"$REPO_ROOT/gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \
-Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \
-Pkotlin.native.cocoapods.archs="$ARCHS" \
-Pkotlin.native.cocoapods.configuration="$CONFIGURATION"
SCRIPT
}
]
spec.resources = ['build/compose/cocoapods/compose-resources']
end

2
gradle-plugins/compose/src/test/test-projects/misc/macosResources/gradle.properties

@ -1,2 +0,0 @@
org.gradle.jvmargs=-Xmx8096M
org.jetbrains.compose.experimental.macos.enabled=true

24
gradle-plugins/compose/src/test/test-projects/misc/macosResources/settings.gradle.kts

@ -1,24 +0,0 @@
rootProject.name = "macosResources"
pluginManagement {
repositories {
mavenLocal()
gradlePluginPortal()
google()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
plugins {
id("org.jetbrains.kotlin.multiplatform").version("KOTLIN_VERSION_PLACEHOLDER")
id("org.jetbrains.kotlin.plugin.compose").version("KOTLIN_VERSION_PLACEHOLDER")
id("org.jetbrains.kotlin.native.cocoapods").version("KOTLIN_VERSION_PLACEHOLDER")
id("org.jetbrains.compose").version("COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER")
}
}
dependencyResolutionManagement {
repositories {
mavenLocal()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
mavenCentral()
gradlePluginPortal()
google()
}
}

36
gradle-plugins/compose/src/test/test-projects/misc/macosResources/src/commonMain/composeResources/drawable/compose-multiplatform.xml

@ -1,36 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="600dp"
android:height="600dp"
android:viewportWidth="600"
android:viewportHeight="600">
<path
android:fillColor="#041619"
android:fillType="nonZero"
android:pathData="M301.21,418.53C300.97,418.54 300.73,418.56 300.49,418.56C297.09,418.59 293.74,417.72 290.79,416.05L222.6,377.54C220.63,376.43 219,374.82 217.85,372.88C216.7,370.94 216.09,368.73 216.07,366.47L216.07,288.16C216.06,287.32 216.09,286.49 216.17,285.67C216.38,283.54 216.91,281.5 217.71,279.6L199.29,268.27L177.74,256.19C175.72,260.43 174.73,265.23 174.78,270.22L174.79,387.05C174.85,393.89 178.57,400.2 184.53,403.56L286.26,461.02C290.67,463.51 295.66,464.8 300.73,464.76C300.91,464.76 301.09,464.74 301.27,464.74C301.24,449.84 301.22,439.23 301.22,439.23L301.21,418.53Z" />
<path
android:fillColor="#37BF6E"
android:fillType="nonZero"
android:pathData="M409.45,242.91L312.64,188.23C303.64,183.15 292.58,183.26 283.68,188.51L187.92,245C183.31,247.73 179.93,251.62 177.75,256.17L177.74,256.19L199.29,268.27L217.71,279.6C217.83,279.32 217.92,279.02 218.05,278.74C218.24,278.36 218.43,277.98 218.64,277.62C219.06,276.88 219.52,276.18 220.04,275.51C221.37,273.8 223.01,272.35 224.87,271.25L289.06,233.39C290.42,232.59 291.87,231.96 293.39,231.51C295.53,230.87 297.77,230.6 300,230.72C302.98,230.88 305.88,231.73 308.47,233.2L373.37,269.85C375.54,271.08 377.49,272.68 379.13,274.57C379.68,275.19 380.18,275.85 380.65,276.53C380.86,276.84 381.05,277.15 381.24,277.47L397.79,266.39L420.34,252.93L420.31,252.88C417.55,248.8 413.77,245.35 409.45,242.91Z" />
<path
android:fillColor="#3870B2"
android:fillType="nonZero"
android:pathData="M381.24,277.47C381.51,277.92 381.77,278.38 382.01,278.84C382.21,279.24 382.39,279.65 382.57,280.06C382.91,280.88 383.19,281.73 383.41,282.59C383.74,283.88 383.92,285.21 383.93,286.57L383.93,361.1C383.96,363.95 383.35,366.77 382.16,369.36C381.93,369.86 381.69,370.35 381.42,370.83C379.75,373.79 377.32,376.27 374.39,378L310.2,415.87C307.47,417.48 304.38,418.39 301.21,418.53L301.22,439.23C301.22,439.23 301.24,449.84 301.27,464.74C306.1,464.61 310.91,463.3 315.21,460.75L410.98,404.25C419.88,399 425.31,389.37 425.22,379.03L425.22,267.85C425.17,262.48 423.34,257.34 420.34,252.93L397.79,266.39L381.24,277.47Z" />
<path
android:fillColor="#00000000"
android:fillType="nonZero"
android:pathData="M177.75,256.17C179.93,251.62 183.31,247.73 187.92,245L283.68,188.51C292.58,183.26 303.64,183.15 312.64,188.23L409.45,242.91C413.77,245.35 417.55,248.8 420.31,252.88L420.34,252.93L498.59,206.19C494.03,199.46 487.79,193.78 480.67,189.75L320.86,99.49C306.01,91.1 287.75,91.27 273.07,99.95L114.99,193.2C107.39,197.69 101.81,204.11 98.21,211.63L177.74,256.19L177.75,256.17ZM301.27,464.74C301.09,464.74 300.91,464.76 300.73,464.76C295.66,464.8 290.67,463.51 286.26,461.02L184.53,403.56C178.57,400.2 174.85,393.89 174.79,387.05L174.78,270.22C174.73,265.23 175.72,260.43 177.74,256.19L98.21,211.63C94.86,218.63 93.23,226.58 93.31,234.82L93.31,427.67C93.42,438.97 99.54,449.37 109.4,454.92L277.31,549.77C284.6,553.88 292.84,556.01 301.2,555.94L301.2,555.8C301.39,543.78 301.33,495.26 301.27,464.74Z"
android:strokeWidth="10"
android:strokeColor="#083042" />
<path
android:fillColor="#00000000"
android:fillType="nonZero"
android:pathData="M498.59,206.19L420.34,252.93C423.34,257.34 425.17,262.48 425.22,267.85L425.22,379.03C425.31,389.37 419.88,399 410.98,404.25L315.21,460.75C310.91,463.3 306.1,464.61 301.27,464.74C301.33,495.26 301.39,543.78 301.2,555.8L301.2,555.94C309.48,555.87 317.74,553.68 325.11,549.32L483.18,456.06C497.87,447.39 506.85,431.49 506.69,414.43L506.69,230.91C506.6,222.02 503.57,213.5 498.59,206.19Z"
android:strokeWidth="10"
android:strokeColor="#083042" />
<path
android:fillColor="#00000000"
android:fillType="nonZero"
android:pathData="M301.2,555.94C292.84,556.01 284.6,553.88 277.31,549.76L109.4,454.92C99.54,449.37 93.42,438.97 93.31,427.67L93.31,234.82C93.23,226.58 94.86,218.63 98.21,211.63C101.81,204.11 107.39,197.69 114.99,193.2L273.07,99.95C287.75,91.27 306.01,91.1 320.86,99.49L480.67,189.75C487.79,193.78 494.03,199.46 498.59,206.19C503.57,213.5 506.6,222.02 506.69,230.91L506.69,414.43C506.85,431.49 497.87,447.39 483.18,456.06L325.11,549.32C317.74,553.68 309.48,555.87 301.2,555.94Z"
android:strokeWidth="10"
android:strokeColor="#083042" />
</vector>

39
gradle-plugins/compose/src/test/test-projects/misc/macosResources/src/commonMain/kotlin/App.kt

@ -1,39 +0,0 @@
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import org.jetbrains.compose.resources.ExperimentalResourceApi
import org.jetbrains.compose.resources.painterResource
import iosResources.generated.resources.*
@OptIn(ExperimentalResourceApi::class)
@Composable
fun App() {
MaterialTheme {
var greetingText by remember { mutableStateOf("Hello, World!") }
var showImage by remember { mutableStateOf(false) }
Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
Button(onClick = {
showImage = !showImage
}) {
Text(greetingText)
}
AnimatedVisibility(showImage) {
Image(
painterResource(Res.drawable.compose_multiplatform),
null
)
}
}
}
}

36
gradle-plugins/compose/src/test/test-projects/misc/macosResources/src/macosMain/composeResources/drawable/icon.xml

@ -1,36 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="600dp"
android:height="600dp"
android:viewportWidth="600"
android:viewportHeight="600">
<path
android:fillColor="#041619"
android:fillType="nonZero"
android:pathData="M301.21,418.53C300.97,418.54 300.73,418.56 300.49,418.56C297.09,418.59 293.74,417.72 290.79,416.05L222.6,377.54C220.63,376.43 219,374.82 217.85,372.88C216.7,370.94 216.09,368.73 216.07,366.47L216.07,288.16C216.06,287.32 216.09,286.49 216.17,285.67C216.38,283.54 216.91,281.5 217.71,279.6L199.29,268.27L177.74,256.19C175.72,260.43 174.73,265.23 174.78,270.22L174.79,387.05C174.85,393.89 178.57,400.2 184.53,403.56L286.26,461.02C290.67,463.51 295.66,464.8 300.73,464.76C300.91,464.76 301.09,464.74 301.27,464.74C301.24,449.84 301.22,439.23 301.22,439.23L301.21,418.53Z" />
<path
android:fillColor="#37BF6E"
android:fillType="nonZero"
android:pathData="M409.45,242.91L312.64,188.23C303.64,183.15 292.58,183.26 283.68,188.51L187.92,245C183.31,247.73 179.93,251.62 177.75,256.17L177.74,256.19L199.29,268.27L217.71,279.6C217.83,279.32 217.92,279.02 218.05,278.74C218.24,278.36 218.43,277.98 218.64,277.62C219.06,276.88 219.52,276.18 220.04,275.51C221.37,273.8 223.01,272.35 224.87,271.25L289.06,233.39C290.42,232.59 291.87,231.96 293.39,231.51C295.53,230.87 297.77,230.6 300,230.72C302.98,230.88 305.88,231.73 308.47,233.2L373.37,269.85C375.54,271.08 377.49,272.68 379.13,274.57C379.68,275.19 380.18,275.85 380.65,276.53C380.86,276.84 381.05,277.15 381.24,277.47L397.79,266.39L420.34,252.93L420.31,252.88C417.55,248.8 413.77,245.35 409.45,242.91Z" />
<path
android:fillColor="#3870B2"
android:fillType="nonZero"
android:pathData="M381.24,277.47C381.51,277.92 381.77,278.38 382.01,278.84C382.21,279.24 382.39,279.65 382.57,280.06C382.91,280.88 383.19,281.73 383.41,282.59C383.74,283.88 383.92,285.21 383.93,286.57L383.93,361.1C383.96,363.95 383.35,366.77 382.16,369.36C381.93,369.86 381.69,370.35 381.42,370.83C379.75,373.79 377.32,376.27 374.39,378L310.2,415.87C307.47,417.48 304.38,418.39 301.21,418.53L301.22,439.23C301.22,439.23 301.24,449.84 301.27,464.74C306.1,464.61 310.91,463.3 315.21,460.75L410.98,404.25C419.88,399 425.31,389.37 425.22,379.03L425.22,267.85C425.17,262.48 423.34,257.34 420.34,252.93L397.79,266.39L381.24,277.47Z" />
<path
android:fillColor="#00000000"
android:fillType="nonZero"
android:pathData="M177.75,256.17C179.93,251.62 183.31,247.73 187.92,245L283.68,188.51C292.58,183.26 303.64,183.15 312.64,188.23L409.45,242.91C413.77,245.35 417.55,248.8 420.31,252.88L420.34,252.93L498.59,206.19C494.03,199.46 487.79,193.78 480.67,189.75L320.86,99.49C306.01,91.1 287.75,91.27 273.07,99.95L114.99,193.2C107.39,197.69 101.81,204.11 98.21,211.63L177.74,256.19L177.75,256.17ZM301.27,464.74C301.09,464.74 300.91,464.76 300.73,464.76C295.66,464.8 290.67,463.51 286.26,461.02L184.53,403.56C178.57,400.2 174.85,393.89 174.79,387.05L174.78,270.22C174.73,265.23 175.72,260.43 177.74,256.19L98.21,211.63C94.86,218.63 93.23,226.58 93.31,234.82L93.31,427.67C93.42,438.97 99.54,449.37 109.4,454.92L277.31,549.77C284.6,553.88 292.84,556.01 301.2,555.94L301.2,555.8C301.39,543.78 301.33,495.26 301.27,464.74Z"
android:strokeWidth="10"
android:strokeColor="#083042" />
<path
android:fillColor="#00000000"
android:fillType="nonZero"
android:pathData="M498.59,206.19L420.34,252.93C423.34,257.34 425.17,262.48 425.22,267.85L425.22,379.03C425.31,389.37 419.88,399 410.98,404.25L315.21,460.75C310.91,463.3 306.1,464.61 301.27,464.74C301.33,495.26 301.39,543.78 301.2,555.8L301.2,555.94C309.48,555.87 317.74,553.68 325.11,549.32L483.18,456.06C497.87,447.39 506.85,431.49 506.69,414.43L506.69,230.91C506.6,222.02 503.57,213.5 498.59,206.19Z"
android:strokeWidth="10"
android:strokeColor="#083042" />
<path
android:fillColor="#00000000"
android:fillType="nonZero"
android:pathData="M301.2,555.94C292.84,556.01 284.6,553.88 277.31,549.76L109.4,454.92C99.54,449.37 93.42,438.97 93.31,427.67L93.31,234.82C93.23,226.58 94.86,218.63 98.21,211.63C101.81,204.11 107.39,197.69 114.99,193.2L273.07,99.95C287.75,91.27 306.01,91.1 320.86,99.49L480.67,189.75C487.79,193.78 494.03,199.46 498.59,206.19C503.57,213.5 506.6,222.02 506.69,230.91L506.69,414.43C506.85,431.49 497.87,447.39 483.18,456.06L325.11,549.32C317.74,553.68 309.48,555.87 301.2,555.94Z"
android:strokeWidth="10"
android:strokeColor="#083042" />
</vector>

3
gradle-plugins/compose/src/test/test-projects/misc/testResources/build.gradle.kts

@ -15,9 +15,6 @@ kotlin {
iosArm64() iosArm64()
iosSimulatorArm64() iosSimulatorArm64()
macosX64()
macosArm64()
sourceSets { sourceSets {
commonMain { commonMain {
dependencies { dependencies {

1
gradle-plugins/compose/src/test/test-projects/misc/testResources/gradle.properties

@ -1,4 +1,3 @@
org.gradle.jvmargs=-Xmx8096M org.gradle.jvmargs=-Xmx8096M
android.useAndroidX=true android.useAndroidX=true
org.jetbrains.compose.experimental.jscanvas.enabled=true org.jetbrains.compose.experimental.jscanvas.enabled=true
org.jetbrains.compose.experimental.macos.enabled=true

2
gradle-plugins/gradle.properties

@ -8,7 +8,7 @@ kotlin.code.style=official
dev.junit.parallel=false dev.junit.parallel=false
# Default version of Compose Libraries used by Gradle plugin # Default version of Compose Libraries used by Gradle plugin
compose.version=1.7.1 compose.version=1.8.0-alpha01
# The latest version of Kotlin compatible with compose.tests.compiler.version. Used only in tests/CI. # The latest version of Kotlin compatible with compose.tests.compiler.version. Used only in tests/CI.
compose.tests.kotlin.version=2.0.0 compose.tests.kotlin.version=2.0.0
# __SUPPORTED_GRADLE_VERSIONS__ # __SUPPORTED_GRADLE_VERSIONS__

216
tools/changelog.main.kts

@ -28,13 +28,15 @@
@file:DependsOn("com.google.code.gson:gson:2.10.1") @file:DependsOn("com.google.code.gson:gson:2.10.1")
import com.google.gson.Gson import com.google.gson.Gson
import java.io.File
import java.io.IOException import java.io.IOException
import java.lang.ProcessBuilder.Redirect
import java.net.URL import java.net.URL
import java.net.URLEncoder
import java.nio.charset.StandardCharsets.UTF_8
import java.time.LocalDate import java.time.LocalDate
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.util.* import java.util.*
import java.util.concurrent.TimeUnit import kotlin.text.substringAfterLast
//region ========================================== CONSTANTS ========================================= //region ========================================== CONSTANTS =========================================
@ -73,7 +75,6 @@ val argsKeyToValue = args
.associate { it.substringBefore("=") to it.substringAfter("=") } .associate { it.substringBefore("=") to it.substringAfter("=") }
val versionCommit = argsKeyless.getOrNull(0) ?: "HEAD" val versionCommit = argsKeyless.getOrNull(0) ?: "HEAD"
val versionName = argsKeyless.getOrNull(1) ?: versionCommit
val token = argsKeyToValue["token"] val token = argsKeyToValue["token"]
println("Note. The script supports optional arguments: kotlin changelog.main.kts [versionCommit] [versionName] [token=githubToken]") println("Note. The script supports optional arguments: kotlin changelog.main.kts [versionCommit] [versionName] [token=githubToken]")
@ -82,6 +83,34 @@ if (token == null) {
} }
println() println()
val androidxLibToVersion = androidxLibToVersion(versionCommit)
val androidxLibToRedirectingVersion = androidxLibToRedirectingVersion(versionCommit)
fun formatAndroidxLibVersion(libName: String) =
androidxLibToVersion[libName] ?: "PLACEHOLDER".also {
println("Can't find $libName version. Using PLACEHOLDER")
}
fun formatAndroidxLibRedirectingVersion(libName: String) =
androidxLibToRedirectingVersion[libName] ?: "PLACEHOLDER".also {
println("Can't find $libName redirecting version. Using PLACEHOLDER")
}
val versionCompose = formatAndroidxLibVersion("COMPOSE")
val versionComposeMaterial3Adaptive = formatAndroidxLibVersion("COMPOSE_MATERIAL3_ADAPTIVE")
val versionLifecycle = formatAndroidxLibVersion("LIFECYCLE")
val versionNavigation = formatAndroidxLibVersion("NAVIGATION")
val versionRedirectingCompose = formatAndroidxLibRedirectingVersion("compose")
val versionRedirectingComposeFoundation = formatAndroidxLibRedirectingVersion("compose.foundation")
val versionRedirectingComposeMaterial = formatAndroidxLibRedirectingVersion("compose.material")
val versionRedirectingComposeMaterial3 = formatAndroidxLibRedirectingVersion("compose.material3")
val versionRedirectingComposeMaterial3Adaptive = formatAndroidxLibRedirectingVersion("compose.material3.adaptive")
val versionRedirectingLifecycle = formatAndroidxLibRedirectingVersion("lifecycle")
val versionRedirectingNavigation = formatAndroidxLibRedirectingVersion("navigation")
val versionName = versionCompose
val currentChangelog = changelogFile.readText() val currentChangelog = changelogFile.readText()
val previousChangelog = val previousChangelog =
if (currentChangelog.startsWith("# $versionName ")) { if (currentChangelog.startsWith("# $versionName ")) {
@ -93,9 +122,10 @@ val previousChangelog =
val previousVersion = previousChangelog.substringAfter("# ").substringBefore(" (") val previousVersion = previousChangelog.substringAfter("# ").substringBefore(" (")
println()
println("Generating changelog between $previousVersion and $versionName") println("Generating changelog between $previousVersion and $versionName")
val newChangelog = getChangelog("v$previousVersion", versionCommit, previousVersion, versionName) val newChangelog = getChangelog("v$previousVersion", versionCommit, previousVersion)
changelogFile.writeText( changelogFile.writeText(
newChangelog + previousChangelog newChangelog + previousChangelog
@ -105,12 +135,12 @@ println()
println("CHANGELOG.md changed") println("CHANGELOG.md changed")
fun getChangelog(firstCommit: String, lastCommit: String, firstVersion: String, lastVersion: String): String { fun getChangelog(firstCommit: String, lastCommit: String, firstVersion: String): String {
val entries = entriesForRepo("JetBrains/compose-multiplatform-core", firstCommit, lastCommit) + val entries = entriesForRepo("JetBrains/compose-multiplatform-core", firstCommit, lastCommit) +
entriesForRepo("JetBrains/compose-multiplatform", firstCommit, lastCommit) entriesForRepo("JetBrains/compose-multiplatform", firstCommit, lastCommit)
return buildString { return buildString {
appendLine("# $lastVersion (${currentChangelogDate()})") appendLine("# $versionName (${currentChangelogDate()})")
appendLine() appendLine()
appendLine("_Changes since ${firstVersion}_") appendLine("_Changes since ${firstVersion}_")
@ -140,16 +170,16 @@ fun getChangelog(firstCommit: String, lastCommit: String, firstVersion: String,
""" """
## Dependencies ## Dependencies
- Gradle Plugin `org.jetbrains.compose`, version `$lastVersion`. Based on Jetpack Compose libraries: - Gradle Plugin `org.jetbrains.compose`, version `$versionCompose`. Based on Jetpack Compose libraries:
- [Runtime REDIRECT_PLACEHOLDER](https://developer.android.com/jetpack/androidx/releases/compose-runtime#REDIRECT_PLACEHOLDER) - [Runtime $versionRedirectingCompose](https://developer.android.com/jetpack/androidx/releases/compose-runtime#$versionRedirectingCompose)
- [UI REDIRECT_PLACEHOLDER](https://developer.android.com/jetpack/androidx/releases/compose-ui#REDIRECT_PLACEHOLDER) - [UI $versionRedirectingCompose](https://developer.android.com/jetpack/androidx/releases/compose-ui#$versionRedirectingCompose)
- [Foundation REDIRECT_PLACEHOLDER](https://developer.android.com/jetpack/androidx/releases/compose-foundation#REDIRECT_PLACEHOLDER) - [Foundation $versionRedirectingComposeFoundation](https://developer.android.com/jetpack/androidx/releases/compose-foundation#$versionRedirectingComposeFoundation)
- [Material REDIRECT_PLACEHOLDER](https://developer.android.com/jetpack/androidx/releases/compose-material#REDIRECT_PLACEHOLDER) - [Material $versionRedirectingComposeMaterial](https://developer.android.com/jetpack/androidx/releases/compose-material#$versionRedirectingComposeMaterial)
- [Material3 REDIRECT_PLACEHOLDER](https://developer.android.com/jetpack/androidx/releases/compose-material3#REDIRECT_PLACEHOLDER) - [Material3 $versionRedirectingComposeMaterial3](https://developer.android.com/jetpack/androidx/releases/compose-material3#$versionRedirectingComposeMaterial3)
- Lifecycle libraries `org.jetbrains.androidx.lifecycle:lifecycle-*:RELEASE_PLACEHOLDER`. Based on [Jetpack Lifecycle REDIRECT_PLACEHOLDER](https://developer.android.com/jetpack/androidx/releases/lifecycle#REDIRECT_PLACEHOLDER) - Lifecycle libraries `org.jetbrains.androidx.lifecycle:lifecycle-*:$versionLifecycle`. Based on [Jetpack Lifecycle $versionRedirectingLifecycle](https://developer.android.com/jetpack/androidx/releases/lifecycle#$versionRedirectingLifecycle)
- Navigation libraries `org.jetbrains.androidx.navigation:navigation-*:RELEASE_PLACEHOLDER`. Based on [Jetpack Navigation REDIRECT_PLACEHOLDER](https://developer.android.com/jetpack/androidx/releases/navigation#REDIRECT_PLACEHOLDER) - Navigation libraries `org.jetbrains.androidx.navigation:navigation-*:$versionNavigation`. Based on [Jetpack Navigation $versionRedirectingNavigation](https://developer.android.com/jetpack/androidx/releases/navigation#$versionRedirectingNavigation)
- Material3 Adaptive libraries `org.jetbrains.compose.material3.adaptive:adaptive*:RELEASE_PLACEHOLDER`. Based on [Jetpack Material3 Adaptive REDIRECT_PLACEHOLDER](https://developer.android.com/jetpack/androidx/releases/compose-material3-adaptive#REDIRECT_PLACEHOLDER) - Material3 Adaptive libraries `org.jetbrains.compose.material3.adaptive:adaptive*:$versionComposeMaterial3Adaptive`. Based on [Jetpack Material3 Adaptive $versionRedirectingComposeMaterial3Adaptive](https://developer.android.com/jetpack/androidx/releases/compose-material3-adaptive#$versionRedirectingComposeMaterial3Adaptive)
--- ---
""".trimIndent() """.trimIndent()
@ -188,13 +218,26 @@ fun currentChangelogDate() = LocalDate.now().format(DateTimeFormatter.ofPattern(
* - [A new approach to implementation of `platformLayers`](link). Now extra layers (such as Dialogs and Popups) drawing is merged into a single screen size canvas. * - [A new approach to implementation of `platformLayers`](link). Now extra layers (such as Dialogs and Popups) drawing is merged into a single screen size canvas.
*/ */
fun ChangelogEntry.format(): String { fun ChangelogEntry.format(): String {
return try {
tryFormat()
} catch (e: Exception) {
throw RuntimeException("Formatting error of ChangelogEntry. Message:\n$message", e)
}
}
fun ChangelogEntry.tryFormat(): String {
return if (link != null) { return if (link != null) {
val prefixRegex = "^[-\\s]*" // "- "
val tagRegex1 = "\\(.*\\)\\s*" // "(something) "
val tagRegex2 = "\\[.*\\]\\s*" // "[something] "
val tagRegex3 = "_.*_\\s*" // "_something_ "
val linkStartIndex = maxOf( val linkStartIndex = maxOf(
message.indexOfFirst { !it.isWhitespace() && it != '-' }.ifNegative { 0 }, message.endIndexOfFirstGroup(Regex("($prefixRegex).*"))?.plus(1) ?: 0,
message.endIndexOf("_(prerelease fix)_ ").ifNegative { 0 }, message.endIndexOfFirstGroup(Regex("($prefixRegex$tagRegex1).*"))?.plus(1) ?: 0,
message.endIndexOf("(prerelease fix) ").ifNegative { 0 }, message.endIndexOfFirstGroup(Regex("($prefixRegex$tagRegex2).*"))?.plus(1) ?: 0,
message.endIndexOfFirstGroup(Regex("($prefixRegex$tagRegex3).*"))?.plus(1) ?: 0,
) )
val linkLastIndex = message.indexOfAny(listOf(". ", " (")).ifNegative { message.length } val linkLastIndex = message.indexOfAny(listOf(". ", " ("), linkStartIndex).ifNegative { message.length }
val beforeLink = message.substring(0, linkStartIndex) val beforeLink = message.substring(0, linkStartIndex)
val inLink = message.substring(linkStartIndex, linkLastIndex).removeLinks() val inLink = message.substring(linkStartIndex, linkLastIndex).removeLinks()
@ -208,13 +251,8 @@ fun ChangelogEntry.format(): String {
fun Int.ifNegative(value: () -> Int): Int = if (this < 0) value() else this fun Int.ifNegative(value: () -> Int): Int = if (this < 0) value() else this
fun String.endIndexOf(value: String): Int = indexOf(value).let { fun String.endIndexOfFirstGroup(regex: Regex): Int? =
if (it >= 0) { regex.find(this)?.groups?.toList()?.getOrNull(1)?.range?.endInclusive
it + value.length
} else {
it
}
}
/** /**
* Converts: * Converts:
@ -243,9 +281,13 @@ fun GitHubPullEntry.extractReleaseNotes(link: String): List<ChangelogEntry> {
before?.trim() before?.trim()
} }
if (relNoteBody?.trim()?.lowercase() == "n/a") return emptyList()
val list = mutableListOf<ChangelogEntry>() val list = mutableListOf<ChangelogEntry>()
var section: String? = null var section: String? = null
var subsection: String? = null var subsection: String? = null
var isFirstLine = true
var shouldPadLines = false
for (line in relNoteBody.orEmpty().split("\n")) { for (line in relNoteBody.orEmpty().split("\n")) {
// parse "### Section - Subsection" // parse "### Section - Subsection"
@ -253,17 +295,30 @@ fun GitHubPullEntry.extractReleaseNotes(link: String): List<ChangelogEntry> {
val s = line.removePrefix("### ") val s = line.removePrefix("### ")
section = s.substringBefore("-", "").trim().normalizeSectionName().ifEmpty { null } section = s.substringBefore("-", "").trim().normalizeSectionName().ifEmpty { null }
subsection = s.substringAfter("-", "").trim().normalizeSubsectionName().ifEmpty { null } subsection = s.substringAfter("-", "").trim().normalizeSubsectionName().ifEmpty { null }
isFirstLine = true
shouldPadLines = false
} else if (section != null && line.isNotBlank()) { } else if (section != null && line.isNotBlank()) {
val isTopLevel = line.startsWith("-") var lineFixed = line
val trimmedLine = line.trimEnd().removeSuffix(".")
if (isFirstLine && !lineFixed.startsWith("-")) {
lineFixed = "- $lineFixed"
shouldPadLines = true
}
if (!isFirstLine && shouldPadLines) {
lineFixed = " $lineFixed"
}
lineFixed = lineFixed.trimEnd().removeSuffix(".")
val isTopLevel = lineFixed.startsWith("-")
list.add( list.add(
ChangelogEntry( ChangelogEntry(
trimmedLine, lineFixed,
section, section,
subsection, subsection,
link.takeIf { isTopLevel } link.takeIf { isTopLevel }
) )
) )
isFirstLine = false
} }
} }
@ -277,7 +332,7 @@ fun GitHubPullEntry.extractReleaseNotes(link: String): List<ChangelogEntry> {
fun entriesForRepo(repo: String, firstCommit: String, lastCommit: String): List<ChangelogEntry> { fun entriesForRepo(repo: String, firstCommit: String, lastCommit: String): List<ChangelogEntry> {
val pulls = (1..5) val pulls = (1..5)
.flatMap { .flatMap {
request<Array<GitHubPullEntry>>("https://api.github.com/repos/$repo/pulls?state=closed&per_page=100&page=$it").toList() requestJson<Array<GitHubPullEntry>>("https://api.github.com/repos/$repo/pulls?state=closed&per_page=100&page=$it").toList()
} }
val pullNumberToPull = pulls.associateBy { it.number } val pullNumberToPull = pulls.associateBy { it.number }
@ -310,8 +365,7 @@ fun entriesForRepo(repo: String, firstCommit: String, lastCommit: String): List<
fun fetchCommits(firsCommitSha: String, lastCommitSha: String): CommitsResult { fun fetchCommits(firsCommitSha: String, lastCommitSha: String): CommitsResult {
lateinit var mergeBaseCommit: String lateinit var mergeBaseCommit: String
val commits = fetchPagedUntilEmpty { page -> val commits = fetchPagedUntilEmpty { page ->
val result = val result = requestJson<GitHubCompareResponse>("https://api.github.com/repos/$repo/compare/$firsCommitSha...$lastCommitSha?per_page=1000&page=$page")
request<GitHubCompareResponse>("https://api.github.com/repos/$repo/compare/$firsCommitSha...$lastCommitSha?per_page=1000&page=$page")
mergeBaseCommit = result.merge_base_commit.sha mergeBaseCommit = result.merge_base_commit.sha
result.commits result.commits
} }
@ -336,6 +390,55 @@ fun repoTitleAndNumberForCommit(commit: GitHubCompareResponse.CommitEntry): Pair
return title to number return title to number
} }
/**
* Extract redirecting versions from core repo, file gradle.properties
*
* Example
* https://raw.githubusercontent.com/JetBrains/compose-multiplatform-core/v1.8.0%2Bdev1966/gradle.properties
* artifactRedirecting.androidx.graphics.version=1.0.1
*/
fun androidxLibToRedirectingVersion(commit: String): Map<String, String> {
val gradleProperties = githubContentOf("JetBrains/compose-multiplatform-core", "gradle.properties", commit)
val regex = Regex("artifactRedirecting\\.androidx\\.(.*)\\.version=(.*)")
return regex.findAll(gradleProperties).associate { result ->
result.groupValues[1].trim() to result.groupValues[2].trim()
}
}
/**
* Extract versions from CI config, file .teamcity/compose/Library.kt
*
* Example
* https://jetbrains.team/p/ui/repositories/compose-teamcity-config/files/8f8408ccd05a9188895969b1fa0243050716baad/.teamcity/compose/Library.kt?tab=source&line=37&lines-count=1
* Library.CORE_BUNDLE -> "1.1.0-alpha01"
*/
fun androidxLibToVersion(commit: String): Map<String, String> {
val repo = "ssh://git@git.jetbrains.team/ui/compose-teamcity-config.git"
val file = ".teamcity/compose/Library.kt"
val libraryKt = spaceContentOf(repo, file, commit)
return if (libraryKt.isBlank()) {
println("Can't clone $repo to know library versions. Please register your ssh key in https://jetbrains.team/m/me/authentication?tab=GitKeys")
emptyMap()
} else {
val regex = Regex("Library\\.(.*)\\s*->\\s*\"(.*)\"")
return regex.findAll(libraryKt).associate { result ->
result.groupValues[1].trim() to result.groupValues[2].trim()
}
}
}
fun githubContentOf(repo: String, path: String, commit: String): String {
val commitEncoded = URLEncoder.encode(commit, UTF_8)
return requestPlain("https://raw.githubusercontent.com/$repo/$commitEncoded/$path")
}
fun spaceContentOf(repoUrl: String, path: String, tagName: String): String {
return pipeProcess("git archive --remote=$repoUrl $tagName $path")
.pipeTo("tar -xO $path")
.readText()
}
data class ChangelogEntry( data class ChangelogEntry(
val message: String, val message: String,
val section: String?, val section: String?,
@ -362,37 +465,25 @@ data class GitHubPullEntry(val number: Int, val title: String, val body: String?
} }
//region ========================================== UTILS ========================================= //region ========================================== UTILS =========================================
fun pipeProcess(command: String) = ProcessBuilder(command.split(" "))
// from https://stackoverflow.com/a/41495542 .redirectOutput(Redirect.PIPE)
fun String.runCommand(workingDir: File = File(".")) { .redirectError(Redirect.PIPE)
ProcessBuilder(*split(" ").toTypedArray()) .start()!!
.directory(workingDir)
.redirectOutput(ProcessBuilder.Redirect.INHERIT) fun Process.pipeTo(command: String): Process = pipeProcess(command).also {
.redirectError(ProcessBuilder.Redirect.INHERIT) inputStream.use { input ->
.start() it.outputStream.use { out ->
.waitFor(5, TimeUnit.MINUTES) input.copyTo(out)
} }
fun String.execCommand(workingDir: File = File(".")): String? {
try {
val parts = this.split("\\s".toRegex())
val proc = ProcessBuilder(*parts.toTypedArray())
.directory(workingDir)
.redirectOutput(ProcessBuilder.Redirect.PIPE)
.redirectError(ProcessBuilder.Redirect.PIPE)
.start()
proc.waitFor(60, TimeUnit.MINUTES)
return proc.inputStream.bufferedReader().readText()
} catch (e: IOException) {
e.printStackTrace()
return null
} }
} }
inline fun <reified T> request( fun Process.readText(): String = inputStream.bufferedReader().use { it.readText() }
url: String
): T = exponentialRetry { inline fun <reified T> requestJson(url: String): T =
Gson().fromJson(requestPlain(url), T::class.java)
fun requestPlain(url: String): String = exponentialRetry {
println("Request $url") println("Request $url")
val connection = URL(url).openConnection() val connection = URL(url).openConnection()
connection.setRequestProperty("User-Agent", "Compose-Multiplatform-Script") connection.setRequestProperty("User-Agent", "Compose-Multiplatform-Script")
@ -400,10 +491,7 @@ inline fun <reified T> request(
connection.setRequestProperty("Authorization", "Bearer $token") connection.setRequestProperty("Authorization", "Bearer $token")
} }
connection.getInputStream().use { connection.getInputStream().use {
Gson().fromJson( it.bufferedReader().readText()
it.bufferedReader(),
T::class.java
)
} }
} }

Loading…
Cancel
Save