diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index df3d892b..122556ca 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,15 +1,35 @@ -name: Deploy to Sonatype +name: Release on: workflow_dispatch -permissions: - contents: read - jobs: - test: - uses: ./.github/workflows/test.yml - deploy: - needs: [test] + draft_release: + permissions: + contents: write + name: Create Draft Release on GitHub + runs-on: ubuntu-latest + outputs: + tag: ${{ steps.tag.outputs.tag }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set tag name + id: tag + run: | + tag=$(basename "${{ github.ref }}") + echo "tag=$tag" >> $GITHUB_OUTPUT + - name: Create Release + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + run: | + tag="${{ steps.tag.outputs.tag }}" + body="Pending release for XCFramework, $tag" + gh release create --draft "$tag" --title "$tag" --notes "$body" + + maven_publish: runs-on: macos-latest steps: - uses: actions/checkout@v4 @@ -38,17 +58,69 @@ jobs: -Ppowersync.binaries.allPlatforms="true" \ publishAllPublicationsToSonatypeRepository shell: bash - # This will change Package.swift in Github packages to direct to new maven central KMMBridge zip file - call-kmmbridge-publish: - needs: deploy + + build_xcframeworks: + name: Build XCFrameworks + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - name: Validate Gradle Wrapper + uses: gradle/wrapper-validation-action@v1 + - uses: actions/cache@v3 + with: + path: ~/.konan + key: ${{ runner.os }}-${{ hashFiles('**/.lock') }} + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v4 + - name: Build frameworks + run: "./gradlew PowerSyncKotlin:buildRelease" + + - uses: actions/upload-artifact@v4 + with: + name: XCFramework + retention-days: 1 # Only used temporarily + compression-level: 0 # We're already uploading a compressed file + path: PowerSyncKotlin/build/FrameworkArchives/PowersyncKotlinRelease.zip + if-no-files-found: error + + add_assets: permissions: contents: write - packages: write - uses: touchlab/KMMBridgeGithubWorkflow/.github/workflows/faktorybuildautoversion.yml@v1.2 - with: - jvmVersion: 17 - versionBaseProperty: SWIFT_LIBRARY_VERSION - publishTask: kmmBridgePublish - secrets: - gradle_params: -PsigningInMemoryKey="${{ secrets.SIGNING_KEY }}" -PsigningInMemoryKeyId="${{ secrets.SIGNING_KEY_ID }}" -PsigningInMemoryKeyPassword="${{ secrets.SIGNING_PASSWORD }}" + needs: [draft_release, build_xcframeworks] + name: Add assets to pending release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: actions/download-artifact@v4 + with: + merge-multiple: true + - run: "ls -al" + - name: Upload XCFramework + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + run: | + gh release upload "${{ needs.draft_release.outputs.tag }}" PowersyncKotlinRelease.zip + - name: "Update release description" + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + shell: bash + run: | + checksums=$(sha256sum PowersyncKotlinRelease.zip) + cat > RELEASE_NOTES <<- NOTES_END + File hashes: + \`\`\` + $checksums + \`\`\` + NOTES_END + + gh release edit "${{ needs.draft_release.outputs.tag }}" -F RELEASE_NOTES diff --git a/Package.swift b/PowerSyncKotlin/Package.swift similarity index 57% rename from Package.swift rename to PowerSyncKotlin/Package.swift index ace85123..e387a08b 100644 --- a/Package.swift +++ b/PowerSyncKotlin/Package.swift @@ -1,6 +1,8 @@ -// swift-tools-version:5.3 -import PackageDescription +// swift-tools-version: 5.7 +// NOTE! This is never released, we're only using this to support local Kotlin SDK builds for the +// Swift SDK. +import PackageDescription let packageName = "PowerSyncKotlin" let package = Package( @@ -12,14 +14,12 @@ let package = Package( products: [ .library( name: packageName, - targets: [packageName] - ), + targets: [packageName]), ], targets: [ .binaryTarget( name: packageName, - path: "./PowerSyncKotlin/build/XCFrameworks/debug/\(packageName).xcframework" + path: "build/XCFrameworks/debug/PowerSyncKotlin.xcframework" ) - , ] -) \ No newline at end of file +) diff --git a/PowerSyncKotlin/build.gradle.kts b/PowerSyncKotlin/build.gradle.kts index 24cb4ca3..797c14c2 100644 --- a/PowerSyncKotlin/build.gradle.kts +++ b/PowerSyncKotlin/build.gradle.kts @@ -1,19 +1,15 @@ -import co.touchlab.faktory.KmmBridgeExtension -import co.touchlab.faktory.artifactmanager.ArtifactManager -import co.touchlab.faktory.capitalized import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget -import java.security.MessageDigest +import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFramework plugins { alias(libs.plugins.kotlinMultiplatform) - alias(libs.plugins.kmmbridge) alias(libs.plugins.skie) - alias(libs.plugins.mavenPublishPlugin) alias(libs.plugins.kotlinter) - id("com.powersync.plugins.sonatype") } kotlin { + val xcf = XCFramework() + listOf( iosX64(), iosArm64(), @@ -22,8 +18,14 @@ kotlin { macosX64(), ).forEach { it.binaries.framework { + baseName = "PowerSyncKotlin" + xcf.add(this) + export(project(":core")) isStatic = true + + binaryOption("bundleId", "PowerSyncKotlin") + binaryOption("bundleVersion", project.version.toString()) } } @@ -64,180 +66,22 @@ configurations.all { } } -kmmbridge { - artifactManager.set(SonatypePortalPublishArtifactManager(project, repositoryName = null)) - artifactManager.finalizeValue() - spm() -} - -// We need this so that when a user includes the package in XCode they are able to -// import the package using Github -if (System.getenv().containsKey("CI")) { - // Setup github publishing based on GitHub action variables - addGithubPackagesRepository() -} - -// This is required for KMMBridge zip to be uploaded to Sonatype (Maven Central) -// Since this will only ever be used in this build file it does not make sense to make a -// plugin to use this. -class SonatypePortalPublishArtifactManager( - val project: Project, - private val publicationName: String = "KMMBridgeFramework", - artifactSuffix: String = "kmmbridge", - private val repositoryName: String? -) : ArtifactManager { - private val group: String = project.group.toString().replace(".", "/") - private val kmmbridgeArtifactId = - "${project.name}-$artifactSuffix" - private val zipName = "powersync-$artifactSuffix" - private val LIBRARY_VERSION: String by project - - // This is the URL that will be added to Package.swift in Github package so that - // KMMBridge is downloaded when a user includes the package in XCode - private val MAVEN_CENTRAL_PACKAGE_ZIP_URL = "https://repo1.maven.org/maven2/com/powersync/${zipName}/${LIBRARY_VERSION}/${zipName}-${LIBRARY_VERSION}.zip" - - override fun deployArtifact( - project: Project, - zipFilePath: File, - version: String - ): String = MAVEN_CENTRAL_PACKAGE_ZIP_URL - - override fun configure( - project: Project, - version: String, - uploadTask: TaskProvider, - kmmPublishTask: TaskProvider - ) { - val zipXCFramework = project.tasks.named("zipXCFramework") - zipXCFramework.configure { - // KMMBridge uses the Gradle Zip tasks to create XCFramework archives, but Gradle - // doesn't support symlinks. XCFrameworks for macOS need to use symlinks though, so we - // patch the task to generate zip files properly. - doLast { - val bridge = project.extensions.getByName("kmmbridge") - val source = project.layout.buildDirectory.map { it.dir("XCFrameworks/${bridge.buildType.get().name}") }.get().asFile - - val out = archiveFile.get().asFile - out.delete() +listOf("Debug", "Release").forEach { buildType -> + tasks.register("build$buildType") { + group = "build" + description = "Create an XCFramework archive for $buildType" - providers.exec { - executable = "zip" - args("-r", "--symlinks", out.absolutePath, "PowerSyncKotlin.xcframework") - workingDir(source) - }.result.get().assertNormalExitValue() - } - } - - project.extensions.getByType().publications.create( - publicationName, - MavenPublication::class.java, - ) { - this.version = version - val archiveProvider = zipXCFramework.flatMap { - it.archiveFile - } - artifact(archiveProvider) { - extension = "zip" - } - artifactId = kmmbridgeArtifactId - } - - // Register the task - project.tasks.register("updatePackageSwiftChecksum") { - artifactId.set(kmmbridgeArtifactId) - zipUrl.set(MAVEN_CENTRAL_PACKAGE_ZIP_URL) - dependsOn("updatePackageSwift") - } - - // Make sure this task runs after updatePackageSwift - project.tasks.named("kmmBridgePublish") { - dependsOn("updatePackageSwiftChecksum") - } + val originalFramework = tasks.getByName("assemblePowerSyncKotlin${buildType}XCFramework") + dependsOn(originalFramework) - publishingTasks().forEach { - uploadTask.configure { - dependsOn(it) - } - } - try { - project.tasks.named("publish").also { task -> - task.configure { - dependsOn(kmmPublishTask) - } - } - } catch (_: UnknownTaskException) { - project.logger.warn("Gradle publish task not found") - } - } - - private fun publishingTasks(): List> { - val publishingExtension = project.extensions.getByType() - - // Either the user has supplied a correct name, or we use the default. If neither is found, fail. - val publicationNameCap = - publishingExtension.publications - .getByName( - publicationName, - ).name - .capitalized() - - return publishingExtension.repositories - .filterIsInstance() - .map { repo -> - val repositoryName = repo.name.capitalized() - val publishTaskName = - "publish${publicationNameCap}PublicationTo${repositoryName}Repository" - // Verify that the "publish" task exists before collecting - project.tasks.named(publishTaskName) - } - } -} - -// This task is used to update Package.swift with the checksum of the zip file -// located on maven central. -abstract class UpdatePackageSwiftChecksumTask : DefaultTask() { - @get:Input - abstract val artifactId: Property - - @get:Input - abstract val zipUrl: Property - - @TaskAction - fun updateChecksum() { - val LIBRARY_VERSION: String by project - - val zipFile = project.file("${project.layout.buildDirectory.get()}/tmp/${artifactId.get().lowercase()}-$LIBRARY_VERSION.zip") - - // Download the zip file - zipFile.parentFile.mkdirs() - project.uri(zipUrl.get()).toURL().openStream().use { input -> - zipFile.outputStream().use { output -> - input.copyTo(output) - } - } - - // Compute the checksum - val checksum = - zipFile.inputStream().use { input -> - val digest = MessageDigest.getInstance("SHA-256") - val buffer = ByteArray(8192) - var bytes = input.read(buffer) - while (bytes >= 0) { - digest.update(buffer, 0, bytes) - bytes = input.read(buffer) - } - digest.digest().joinToString("") { "%02x".format(it) } - } + val source = project.layout.buildDirectory.map { it.dir("XCFrameworks/${buildType.lowercase()}") }.get().asFile + val archiveFile = project.layout.buildDirectory.map { it.file("FrameworkArchives/PowersyncKotlin$buildType.zip") }.get().asFile - // Update Package.swift - val packageSwiftFile = project.rootProject.file("Package.swift") - val updatedContent = - packageSwiftFile.readText().replace( - Regex("let remoteKotlinChecksum = \"[a-f0-9]+\""), - "let remoteKotlinChecksum = \"$checksum\"", - ) - packageSwiftFile.writeText(updatedContent) + archiveFile.parentFile.mkdirs() + archiveFile.delete() - println("Updated Package.swift with new checksum: $checksum") + executable = "zip" + args("-r", "--symlinks", archiveFile.absolutePath, "PowerSyncKotlin.xcframework") + workingDir(source) } } diff --git a/build.gradle.kts b/build.gradle.kts index 17e391ee..b8bff3d1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,8 +10,6 @@ plugins { alias(libs.plugins.androidApplication) apply false alias(libs.plugins.androidLibrary) apply false alias(libs.plugins.kotlinMultiplatform) apply false - alias(libs.plugins.cocoapods) apply false - alias(libs.plugins.kmmbridge) apply false alias(libs.plugins.skie) apply false alias(libs.plugins.kotlin.jvm) apply false alias(libs.plugins.kotlin.android) apply false diff --git a/docs/Release.md b/docs/Release.md index 958848d9..bd59291d 100644 --- a/docs/Release.md +++ b/docs/Release.md @@ -2,7 +2,15 @@ ## How to make a release -1. Update `LIBRARY_VERSION` and `SWIFT_LIBRARY_VERSION` in `gradle.properties` in the root. +1. Update `LIBRARY_VERSION` in `gradle.properties` in the root. 2. Add an entry to the `CHANGELOG.md`. 3. Make a PR and merge it. -4. Once the PR is merged and in the `main` branch then manually run the Github action `Deploy to Sonatype`. This will create a release to Maven Central and will also update the version of the `powersync-kotlin` SPM package used in the Swift SDK. If the release contains changes pertaining to the Swift SDK you will need to update the `powersync-kotlin` SPM package version in that repo and make a release there as well. +4. Pull `main` (which now contains your merged PR) and create a tag matching the version, e.g. + `git tag v1.1.0`. +5. Push that tag and manually trigger the GitHub action `Release` on that tag. This will: + - Create a release to Maven Central. + - Create a draft release on Github. + - Build and attach an `XCFramework` zip archive for the Swift SDK to the draft release. +6. Copy relevant entries from the changelog into the draft release and make it public! +7. To apply this release to the Swift SDK, update the `Package.swift` file to point at the framework + from that release. You can copy the SHA256 hashsum from the generated draft release notes. diff --git a/gradle.properties b/gradle.properties index 03fe5a6e..47dddbbe 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,10 +18,6 @@ RELEASE_SIGNING_ENABLED=true # Library config GROUP=com.powersync LIBRARY_VERSION=1.1.0 -# The Swift KMM bridge artifacts are published to SPM via a unique tag version -# The version is the same as the LIBRARY_VERSION, but with a suffix of +SWIFT -# Please update this when updating the LIBRARY_VERSION -SWIFT_LIBRARY_VERSION=1.1.0+SWIFT GITHUB_REPO=https://github.com/powersync-ja/powersync-kotlin.git # POM POM_URL=https://github.com/powersync-ja/powersync-kotlin/ diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a617db08..ef5207fe 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -34,7 +34,6 @@ androidxSqlite = "2.4.0" # plugins android-gradle-plugin = "8.9.0" -kmmBridge = "0.5.7" skie = "0.10.1" maven-publish = "0.27.0" download-plugin = "5.5.0" @@ -125,7 +124,6 @@ kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } skie = { id = "co.touchlab.skie", version.ref = "skie" } -kmmbridge = { id = "co.touchlab.kmmbridge", version.ref = "kmmBridge" } sqldelight = { id = "app.cash.sqldelight", version.ref = "sqlDelight" } grammarKitComposer = { id = "com.alecstrong.grammar.kit.composer", version.ref = "grammarkit-composer" } mavenPublishPlugin = { id = "com.vanniktech.maven.publish", version.ref = "maven-publish" }