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. 27
      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. 17
      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. 3
      gradle-plugins/compose/src/test/test-projects/misc/testResources/gradle.properties
  65. 2
      gradle-plugins/gradle.properties
  66. 218
      tools/changelog.main.kts

8
.github/PULL_REQUEST_TEMPLATE.md

@ -15,16 +15,18 @@ This should be tested by QA
## 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
- Known Issues
- Breaking Changes
- Features
- Fixes
Subsections:
Possible subsections:
- Multiple Platforms
- iOS
- 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)
[![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)
[![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)
[![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)
[![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

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

27
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.TaskAction
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.ProjectIdentifier
import java.util.regex.Pattern
abstract class FindModulesInSpaceTask : DefaultTask() {
@get:Input
abstract val requestedGroupId: Property<String>
@get:Input
abstract val requestedVersion: Property<String>
abstract val requestedCoordinates: Property<String>
@get:Input
abstract val spaceInstanceUrl: Property<String>
@ -52,11 +51,14 @@ abstract class FindModulesInSpaceTask : DefaultTask() {
val projectId = ProjectIdentifier.Id(spaceProjectId.get())
val repoId = PackageRepositoryIdentifier.Id(spaceRepoId.get())
val modules = ArrayList<String>()
val requestedGroupId = requestedGroupId.get()
val requestedVersion = requestedVersion.get()
space.forEachPackageWithVersion(projectId, repoId, requestedVersion) { pkg ->
if (pkg.groupId.startsWith(requestedGroupId)) {
modules.add("${pkg.groupId}:${pkg.artifactId}:${pkg.version}")
val requestedCoordinates = requestedCoordinates.get().split(",")
requestedCoordinates.forEach { req ->
val version = req.substringAfterLast(":") // suppose we don't support wildcards in version
space.forEachPackageWithVersion(projectId, repoId, version) { pkg ->
val pkgStr = pkg.toString()
if (pkgStr.matchesWildcard(req)) {
modules.add(pkgStr)
}
}
}
@ -65,4 +67,9 @@ abstract class FindModulesInSpaceTask : DefaultTask() {
writeText(modules.joinToString("\n"))
}
}
}
}
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
class MavenCentralProperties(private val myProject: Project) {
val version: Provider<String> =
propertyProvider("maven.central.version")
val coordinates: Provider<String> =
propertyProvider("maven.central.coordinates")
val stage: Provider<String> =
propertyProvider("maven.central.stage")
val description: Provider<String> =
propertyProvider("maven.central.description")
val user: Provider<String> =
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>
@get:Internal
abstract val autoCommitOnSuccess: Property<Boolean>
abstract val stagingDescription: Property<String>
@get:Internal
abstract val version: Property<String>
abstract val autoCommitOnSuccess: Property<Boolean>
@get:Internal
abstract val modulesToUpload: ListProperty<ModuleToUpload>
@ -59,7 +59,7 @@ abstract class UploadToSonatypeTask : DefaultTask() {
validate(stagingProfile, modules)
val stagingRepo = sonatype.createStagingRepo(
stagingProfile, "Staging repo for '${stagingProfile.name}' release '${version.get()}'"
stagingProfile, stagingDescription.get()
)
try {
for (module in modules) {
@ -76,7 +76,7 @@ abstract class UploadToSonatypeTask : DefaultTask() {
private fun validate(stagingProfile: StagingProfile, modules: List<ModuleToUpload>) {
val validationIssues = arrayListOf<Pair<ModuleToUpload, ModuleValidator.Status.Error>>()
for (module in modules) {
val status = ModuleValidator(stagingProfile, module, version.get()).validate()
val status = ModuleValidator(stagingProfile, module).validate()
if (status is ModuleValidator.Status.Error) {
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(
private val stagingProfile: StagingProfile,
private val module: ModuleToUpload,
private val version: String
) {
private val errors = arrayListOf<String>()
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}'")
}
if (module.version != version) {
errors.add("Unexpected version '${module.version}' (expected: '$version')")
}
val pomFile = artifactFile(extension = "pom")
val pom = when {
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 artifactId: String,
val version: String
)
) {
override fun toString() = "$groupId:$artifactId:$version"
}
fun forEachPackageWithVersion(
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
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
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
kotlin.version=1.9.24
agp.version=8.2.2
compose.version=1.7.0
compose.version=1.8.0-alpha01
deploy.version=0.1.0-SNAPSHOT
#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.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Slider
import androidx.compose.material3.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 androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontVariation
import androidx.compose.ui.unit.dp
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 kotlin.math.roundToInt
@Composable
fun FontRes(paddingValues: PaddingValues) {
@ -74,5 +87,56 @@ fun FontRes(paddingValues: PaddingValues) {
style = MaterialTheme.typography.headlineLarge,
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")
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,10 +5,27 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.*
@Deprecated(
message = "Use the new Font function with variationSettings instead.",
level = DeprecationLevel.HIDDEN
)
@Composable
actual fun Font(resource: FontResource, weight: FontWeight, style: FontStyle): 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)
}
@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

@ -46,4 +46,21 @@ internal actual fun <T> rememberResourceState(
runBlocking { block(environment) }
)
}
}
@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
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.
@ -28,6 +28,10 @@ class FontResource
*
* @throws NotFoundException if the specified resource ID is not found.
*/
@Deprecated(
message = "Use the new Font function with variationSettings instead.",
level = DeprecationLevel.HIDDEN
)
@Composable
expect fun Font(
resource: FontResource,
@ -35,6 +39,26 @@ expect fun Font(
style: FontStyle = FontStyle.Normal
): 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.
*

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

@ -40,4 +40,20 @@ internal expect fun <T> rememberResourceState(
key3: Any,
getDefault: () -> T,
block: suspend (ResourceEnvironment) -> 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.ExperimentalEncodingApi
private val SimpleStringFormatRegex = Regex("""%(\d)\$[ds]""")
private val SimpleStringFormatRegex = Regex("""%(\d+)\$[ds]""")
internal fun String.replaceWithArgs(args: List<String>) = SimpleStringFormatRegex.replace(this) { matchResult ->
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 {
val fm = NSFileManager.defaultManager()
val currentDirectoryPath = fm.currentDirectoryPath
val pathFix = getPathWithoutPackage(path)
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)
"$currentDirectoryPath/src/macosMain/composeResources/$pathFix",
"$currentDirectoryPath/src/macosTest/composeResources/$pathFix",
"$currentDirectoryPath/src/commonMain/composeResources/$pathFix",
"$currentDirectoryPath/src/commonTest/composeResources/$pathFix"
"$currentDirectoryPath/src/macosMain/composeResources/$path",
"$currentDirectoryPath/src/macosTest/composeResources/$path",
"$currentDirectoryPath/src/commonMain/composeResources/$path",
"$currentDirectoryPath/src/commonTest/composeResources/$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.ui.text.font.*
import androidx.compose.ui.text.platform.Font
import androidx.compose.ui.unit.Density
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi
@ -33,6 +34,10 @@ private val fontCache = AsyncCache<String, Font>()
internal val Font.isEmptyPlaceholder: Boolean
get() = this == defaultEmptyFont
@Deprecated(
message = "Use the new Font function with variationSettings instead.",
level = DeprecationLevel.HIDDEN
)
@Composable
actual fun Font(resource: FontResource, weight: FontWeight, style: FontStyle): Font {
val resourceReader = LocalResourceReader.currentOrPreview
@ -45,4 +50,31 @@ actual fun Font(resource: FontResource, weight: FontWeight, style: FontStyle): F
}
}
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.text.font.Font
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontVariation
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 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 A [State]<[Font]?> object that holds the loaded [Font] when available,
* or `null` if the font is not yet ready.
*/
@ -98,10 +100,11 @@ internal fun getResourceUrl(windowOrigin: String, windowPathname: String, resour
fun preloadFont(
resource: FontResource,
weight: FontWeight = FontWeight.Normal,
style: FontStyle = FontStyle.Normal
style: FontStyle = FontStyle.Normal,
variationSettings: FontVariation.Settings = FontVariation.Settings(weight, style),
): State<Font?> {
val resState = remember(resource, weight, style) { mutableStateOf<Font?>(null) }.apply {
value = Font(resource, weight, style).takeIf { !it.isEmptyPlaceholder }
val resState = remember(resource, weight, style, variationSettings) { mutableStateOf<Font?>(null) }.apply {
value = Font(resource, weight, style, variationSettings).takeIf { !it.isEmptyPlaceholder }
}
return resState
}

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

@ -60,4 +60,24 @@ internal actual fun <T> rememberResourceState(
}
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 " +
"@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
}
}

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

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") -> {
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'")
else -> error("Unknown iOS platform: '$platform'")
}
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")
)
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"))
checkResourcesZip(jsResZip, resourcesFiles, false)
@ -307,13 +300,6 @@ class ResourcesTest : GradlePluginTestBase() {
":appModule:iosSimulatorArm64Test"
}
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 ->
@ -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
fun iosTestResources() {
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
fun checkTestResources() {
with(testProject("misc/testResources")) {
@ -832,10 +714,6 @@ class ResourcesTest : GradlePluginTestBase() {
check.logContains("Configure test resources for 'iosArm64' target")
check.logContains("Configure main 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")
}

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

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

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

@ -13,8 +13,6 @@ kotlin {
iosX64()
iosArm64()
iosSimulatorArm64()
macosX64()
macosArm64()
js { 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()
iosArm64()
iosSimulatorArm64()
macosX64()
macosArm64()
js { browser() }
wasmJs { browser() }

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

@ -11,8 +11,6 @@ kotlin {
iosX64()
iosArm64()
iosSimulatorArm64()
macosX64()
macosArm64()
js { 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.jscanvas.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()
iosSimulatorArm64()
macosX64()
macosArm64()
sourceSets {
commonMain {
dependencies {

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

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

2
gradle-plugins/gradle.properties

@ -8,7 +8,7 @@ kotlin.code.style=official
dev.junit.parallel=false
# 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.
compose.tests.kotlin.version=2.0.0
# __SUPPORTED_GRADLE_VERSIONS__

218
tools/changelog.main.kts

@ -28,13 +28,15 @@
@file:DependsOn("com.google.code.gson:gson:2.10.1")
import com.google.gson.Gson
import java.io.File
import java.io.IOException
import java.lang.ProcessBuilder.Redirect
import java.net.URL
import java.net.URLEncoder
import java.nio.charset.StandardCharsets.UTF_8
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.util.*
import java.util.concurrent.TimeUnit
import kotlin.text.substringAfterLast
//region ========================================== CONSTANTS =========================================
@ -73,7 +75,6 @@ val argsKeyToValue = args
.associate { it.substringBefore("=") to it.substringAfter("=") }
val versionCommit = argsKeyless.getOrNull(0) ?: "HEAD"
val versionName = argsKeyless.getOrNull(1) ?: versionCommit
val token = argsKeyToValue["token"]
println("Note. The script supports optional arguments: kotlin changelog.main.kts [versionCommit] [versionName] [token=githubToken]")
@ -82,6 +83,34 @@ if (token == null) {
}
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 previousChangelog =
if (currentChangelog.startsWith("# $versionName ")) {
@ -93,9 +122,10 @@ val previousChangelog =
val previousVersion = previousChangelog.substringAfter("# ").substringBefore(" (")
println()
println("Generating changelog between $previousVersion and $versionName")
val newChangelog = getChangelog("v$previousVersion", versionCommit, previousVersion, versionName)
val newChangelog = getChangelog("v$previousVersion", versionCommit, previousVersion)
changelogFile.writeText(
newChangelog + previousChangelog
@ -105,12 +135,12 @@ println()
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) +
entriesForRepo("JetBrains/compose-multiplatform", firstCommit, lastCommit)
return buildString {
appendLine("# $lastVersion (${currentChangelogDate()})")
appendLine("# $versionName (${currentChangelogDate()})")
appendLine()
appendLine("_Changes since ${firstVersion}_")
@ -140,16 +170,16 @@ fun getChangelog(firstCommit: String, lastCommit: String, firstVersion: String,
"""
## Dependencies
- Gradle Plugin `org.jetbrains.compose`, version `$lastVersion`. Based on Jetpack Compose libraries:
- [Runtime REDIRECT_PLACEHOLDER](https://developer.android.com/jetpack/androidx/releases/compose-runtime#REDIRECT_PLACEHOLDER)
- [UI REDIRECT_PLACEHOLDER](https://developer.android.com/jetpack/androidx/releases/compose-ui#REDIRECT_PLACEHOLDER)
- [Foundation REDIRECT_PLACEHOLDER](https://developer.android.com/jetpack/androidx/releases/compose-foundation#REDIRECT_PLACEHOLDER)
- [Material REDIRECT_PLACEHOLDER](https://developer.android.com/jetpack/androidx/releases/compose-material#REDIRECT_PLACEHOLDER)
- [Material3 REDIRECT_PLACEHOLDER](https://developer.android.com/jetpack/androidx/releases/compose-material3#REDIRECT_PLACEHOLDER)
- 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)
- 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)
- 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)
- Gradle Plugin `org.jetbrains.compose`, version `$versionCompose`. Based on Jetpack Compose libraries:
- [Runtime $versionRedirectingCompose](https://developer.android.com/jetpack/androidx/releases/compose-runtime#$versionRedirectingCompose)
- [UI $versionRedirectingCompose](https://developer.android.com/jetpack/androidx/releases/compose-ui#$versionRedirectingCompose)
- [Foundation $versionRedirectingComposeFoundation](https://developer.android.com/jetpack/androidx/releases/compose-foundation#$versionRedirectingComposeFoundation)
- [Material $versionRedirectingComposeMaterial](https://developer.android.com/jetpack/androidx/releases/compose-material#$versionRedirectingComposeMaterial)
- [Material3 $versionRedirectingComposeMaterial3](https://developer.android.com/jetpack/androidx/releases/compose-material3#$versionRedirectingComposeMaterial3)
- 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-*:$versionNavigation`. Based on [Jetpack Navigation $versionRedirectingNavigation](https://developer.android.com/jetpack/androidx/releases/navigation#$versionRedirectingNavigation)
- 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()
@ -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.
*/
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) {
val prefixRegex = "^[-\\s]*" // "- "
val tagRegex1 = "\\(.*\\)\\s*" // "(something) "
val tagRegex2 = "\\[.*\\]\\s*" // "[something] "
val tagRegex3 = "_.*_\\s*" // "_something_ "
val linkStartIndex = maxOf(
message.indexOfFirst { !it.isWhitespace() && it != '-' }.ifNegative { 0 },
message.endIndexOf("_(prerelease fix)_ ").ifNegative { 0 },
message.endIndexOf("(prerelease fix) ").ifNegative { 0 },
message.endIndexOfFirstGroup(Regex("($prefixRegex).*"))?.plus(1) ?: 0,
message.endIndexOfFirstGroup(Regex("($prefixRegex$tagRegex1).*"))?.plus(1) ?: 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 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 String.endIndexOf(value: String): Int = indexOf(value).let {
if (it >= 0) {
it + value.length
} else {
it
}
}
fun String.endIndexOfFirstGroup(regex: Regex): Int? =
regex.find(this)?.groups?.toList()?.getOrNull(1)?.range?.endInclusive
/**
* Converts:
@ -243,9 +281,13 @@ fun GitHubPullEntry.extractReleaseNotes(link: String): List<ChangelogEntry> {
before?.trim()
}
if (relNoteBody?.trim()?.lowercase() == "n/a") return emptyList()
val list = mutableListOf<ChangelogEntry>()
var section: String? = null
var subsection: String? = null
var isFirstLine = true
var shouldPadLines = false
for (line in relNoteBody.orEmpty().split("\n")) {
// parse "### Section - Subsection"
@ -253,17 +295,30 @@ fun GitHubPullEntry.extractReleaseNotes(link: String): List<ChangelogEntry> {
val s = line.removePrefix("### ")
section = s.substringBefore("-", "").trim().normalizeSectionName().ifEmpty { null }
subsection = s.substringAfter("-", "").trim().normalizeSubsectionName().ifEmpty { null }
isFirstLine = true
shouldPadLines = false
} else if (section != null && line.isNotBlank()) {
val isTopLevel = line.startsWith("-")
val trimmedLine = line.trimEnd().removeSuffix(".")
var lineFixed = line
if (isFirstLine && !lineFixed.startsWith("-")) {
lineFixed = "- $lineFixed"
shouldPadLines = true
}
if (!isFirstLine && shouldPadLines) {
lineFixed = " $lineFixed"
}
lineFixed = lineFixed.trimEnd().removeSuffix(".")
val isTopLevel = lineFixed.startsWith("-")
list.add(
ChangelogEntry(
trimmedLine,
lineFixed,
section,
subsection,
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> {
val pulls = (1..5)
.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 }
@ -310,8 +365,7 @@ fun entriesForRepo(repo: String, firstCommit: String, lastCommit: String): List<
fun fetchCommits(firsCommitSha: String, lastCommitSha: String): CommitsResult {
lateinit var mergeBaseCommit: String
val commits = fetchPagedUntilEmpty { page ->
val result =
request<GitHubCompareResponse>("https://api.github.com/repos/$repo/compare/$firsCommitSha...$lastCommitSha?per_page=1000&page=$page")
val result = requestJson<GitHubCompareResponse>("https://api.github.com/repos/$repo/compare/$firsCommitSha...$lastCommitSha?per_page=1000&page=$page")
mergeBaseCommit = result.merge_base_commit.sha
result.commits
}
@ -336,6 +390,55 @@ fun repoTitleAndNumberForCommit(commit: GitHubCompareResponse.CommitEntry): Pair
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(
val message: String,
val section: String?,
@ -362,37 +465,25 @@ data class GitHubPullEntry(val number: Int, val title: String, val body: String?
}
//region ========================================== UTILS =========================================
// from https://stackoverflow.com/a/41495542
fun String.runCommand(workingDir: File = File(".")) {
ProcessBuilder(*split(" ").toTypedArray())
.directory(workingDir)
.redirectOutput(ProcessBuilder.Redirect.INHERIT)
.redirectError(ProcessBuilder.Redirect.INHERIT)
.start()
.waitFor(5, TimeUnit.MINUTES)
}
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
fun pipeProcess(command: String) = ProcessBuilder(command.split(" "))
.redirectOutput(Redirect.PIPE)
.redirectError(Redirect.PIPE)
.start()!!
fun Process.pipeTo(command: String): Process = pipeProcess(command).also {
inputStream.use { input ->
it.outputStream.use { out ->
input.copyTo(out)
}
}
}
inline fun <reified T> request(
url: String
): T = exponentialRetry {
fun Process.readText(): String = inputStream.bufferedReader().use { it.readText() }
inline fun <reified T> requestJson(url: String): T =
Gson().fromJson(requestPlain(url), T::class.java)
fun requestPlain(url: String): String = exponentialRetry {
println("Request $url")
val connection = URL(url).openConnection()
connection.setRequestProperty("User-Agent", "Compose-Multiplatform-Script")
@ -400,10 +491,7 @@ inline fun <reified T> request(
connection.setRequestProperty("Authorization", "Bearer $token")
}
connection.getInputStream().use {
Gson().fromJson(
it.bufferedReader(),
T::class.java
)
it.bufferedReader().readText()
}
}

Loading…
Cancel
Save