Skip to content

Conversation

mohannad-hassan
Copy link
Collaborator

@mohannad-hassan mohannad-hassan commented Jul 20, 2025

  • Provides workflows to automatically generate dev builds on merges
  • iOS packages will be hosted on this repo's releases.
  • There is a separate workflow to generate the more stable builds.

@mohannad-hassan mohannad-hassan changed the title Export "umbrella" as an SPM Provide a Swift package & workflows to generate iOS builds Sep 12, 2025
@mohannad-hassan mohannad-hassan changed the title Provide a Swift package & workflows to generate iOS builds Provide a Swift package - Workflows to generate iOS builds Sep 13, 2025
Copy link

@mohamede1945 mohamede1945 left a comment

Choose a reason for hiding this comment

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

Thank you for your work on this!

Comment on lines 24 to 30
.binaryTarget(
name: "QuranSyncUmbrella",
url: "https://github.com/quran/mobile-sync/releases/download/{VERSION}/QuranSyncUmbrella.xcframework.zip",
checksum: "{CHECKSUM_TO_BE_REPLACED_BY_CI}"
)
]
)

Choose a reason for hiding this comment

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

As per our discussion and https://www.jetbrains.com/help/kotlin-multiplatform-dev/multiplatform-spm-export.html#set-up-remote-integration, we need a separate repo to host the package.swift.

Store the Package.swift file and the code that should be packaged into an XCFramework in separate Git repositories. This allows versioning the Swift manifest separately from the project the file describes. This is the recommended approach: it allows scaling and is generally easier to maintain.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I fail to see why we need to separate the Swift's package definition and generated XCFramework hosting to a separate repo.

JetBrains mentions the approach used here as the second approach, and the drawback mentioned is:

... but keep in mind that, in this case, the Swift package and the code will use the same versioning. SPM uses Git tags for versioning packages, which can conflict with tags used for your project.

This is actually one of the reasons I resorted to keeping both in the same repo. Whenever the library's components the exported library will have to be updated, so the Swift package will follow the KMP packages versions.

We may have some problems if we ever reach the point that the Kotlin packages will need to have different versions.

Let me know what you think.

Choose a reason for hiding this comment

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

This PR is running into the exact issue described in the approach: "SPM uses Git tags for versioning packages, which can conflict with tags used for your project."

To work around it, the code introduces a non-standard workflow: creating a release and then immediately committing another change to bump Package.swift for every single change in the repo.

This approach is problematic because it deviates from standard git practices, clutters the commit history, and distorts the meaning of releases.

My suggestion is to go with the recommended approach for creating a separate repo for the package.swift, please.

Copy link
Collaborator Author

@mohannad-hassan mohannad-hassan Sep 16, 2025

Choose a reason for hiding this comment

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

What I understood from that is when the Swift package's versioning would collide with the KMP project's versioning, which we don't have. I see that would happen if the main repo needed to handle other concerns that those exposed for iOS, e.g. if it did contain the final app code, etc...

As for the separate version commit, I believe we will have to do them whenever we handle versioning for the modules here, as well. It would be common to set the version in the gradle files, and edit change logs, so it'd be x.y.z version bump for both gradle and Package.swift, and hence any other needed artifacts.

We could see this behavior in some of the Kotlin dependencies we're using: ktor-3.2.1, ktor-3.3.0, kermit-2.0.8 and sqldelight-2.1.0.

These don't provide a Swift package for a native KMP package, but it shows they're following the same_-ish_ versioning technique we might be following, and since we're not developing anything else here, then it follows that versioning for the Swift package and framework can be de done here and track the same versions.

Copy link
Collaborator Author

@mohannad-hassan mohannad-hassan Sep 16, 2025

Choose a reason for hiding this comment

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

The main drawback I see will be if we had to drive versioning of the data repositories and the sync agent separately. If that's the case we will need to get the umbrella Kotlin package out with the Swift package as well.

Edit: even if we have to provide different versions for different libraries, we could go with a prefixed tag strategy. Maybe then I'd see the need for a separate repo for the Swift package as it'd have a new version each time any of the individual modules gets pumped to a new version.

But I don't expect we'd reach that stage, at least soon.

Copy link
Collaborator Author

@mohannad-hassan mohannad-hassan Sep 21, 2025

Choose a reason for hiding this comment

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

package.swift will always be updated to point to the last created git tag after the fact and hence package.swift will not really be part of the tag's content.

Why would that be the case? Creating a new release will build the needed artifacts, creates the Github release, pumps the version values and commits. We don't currently put the version in Gradle files, but it'd be the same logic.

I depended on Claude to generate the workflows and script files, but I looked it over multiple times and it seems alright. Am I missing something here?


The production of the xcframework should be enough to verify the library builds

I meant the value of continuous integration. If we have the responsibility of building the framework in the same repo, then we can have that check executed on each PR alongside tests.

I don't expect this to be a frequent problem, but it's just an example of what we lose when we separate Package.swift and XCFramework generation in a separate repo.


In both cases, we need to update the package.swift to work locally either with env vars or updating hardcoded values. But the package.swift will need to change

As things are set up now, quran-sync/Package.swift should not be changed when building locally. The env-var set from the host project (i.e. quran-ios) will control if it points to the local package or the remote XCFramework, so the change will be made from the importing project.

The point is, as I see things now, importing the project should be a bit simpler.


Still the main point is what I said about my interpretation of jetbrains' recommendation. It's recommended within the context of exposing few modules from an existing bigger project to an iOS framework. This is my opinion, so I can't find references for that. My clues for that: 1. Jetbrains did mention my approach here as the second recommendation, 2. and the drawback they mentioned does not exist in our case, as I see it.

Moreover, if our usage or the internal structure change, we can easily migrate inshaa Allah.

Choose a reason for hiding this comment

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

Salam alikom, Mohannad, let's look at the steps. Let's assume we will not create a new release on each commit because this will leave the repo with just automated releases and commits and make it extremely hard to work with. Let's assume we can manually trigger the release.yml workflow whenever we need to release. The steps are:

  1. Build, test and generate xcframework
  2. Update package.swift
  3. Commit
  4. Create a tag
  5. Push the commit
  6. Push the tag
  7. Create a new github release and upload the xcframework

The problem here is that, package.swift was created and pushed to the repo before xcframework was uploaded. Now, that means we could potentially be in a situation where package.swift point to a non-existent xcframework which is problematic. This is the main major issue, with multiple repos we upload xcframework first and then update package.swift.

There are other issues like the automated commits and automated releases in the mobile-sync repo which is supposed to be not just about releasing new package.swift.

Copy link
Collaborator Author

@mohannad-hassan mohannad-hassan Sep 22, 2025

Choose a reason for hiding this comment

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

That's not what happens now. The workflow is either triggered from Github, or triggered from scripts/release which does the same thing, so we won't be doing the steps manually, if that's the point.

  • The Github release is made
  • The framework is built and added to the release
  • Package.swift is updated and committed
  • The tag is set for that commit.

I've tested that out on a fork. See the commit and the release. If we needed to update other artifacts with version numbers, we should be able to do that in the same workflow and add it to the same commit.

Choose a reason for hiding this comment

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

You can't create a github release before creating a tag.

Choose a reason for hiding this comment

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

Again, the problem here is not that the approach works or not. The problem here is the approach is brittle and we can easily be in an invalid state.

At this point, I feel we have gone in circles long enough and I don't think is more to say here. Let's revisit this in the team meeting tomorrow.

Comment on lines 86 to 92
echo "Usage: $0 {local|remote|build|dev|list-dev}"
echo " local - Switch to local development mode"
echo " remote - Switch to remote dependencies"
echo " build - Build XCFramework only"
echo " dev <ver> - Switch to specific dev build"
echo " list-dev - List available dev builds"
exit 1

Choose a reason for hiding this comment

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

I don't think we need all these functionality. We probably just need a build step that's executed as pre build of the xcode project. Switching from local to remote should simply edit the package.swift file or set an environment variable. I don't think we need a script for that as it can easily go out of sync if someone is not using it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'll probably rewrite something specific to the need when I migrate this to quran-ios, in shaa Allah.

Package.swift Outdated
import PackageDescription

// Set to true for local development
let useLocalBuild = false

Choose a reason for hiding this comment

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

I haven't tried this myself but we probably could overwrite this with an env var and have it default to false. See https://gist.github.com/Sorix/21e61347f478ae2e83ef4d8a92d933af

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It's clear from mobile-sync end.

From quran-ios's side, we will need to point to the local path as well. I've experimented with this and found that the best way to control both Package.swift files is through setting the evn var from Terminal before opening Xcode. Otherwise, setting it through the scheme's variables won't reflect in package resolution.

It will look something like this:

let useLocalQuranSync = ProcessInfo.processInfo.environment["QURAN_SYNC_LOCAL_BUILD"] == "true"
// ...
// ...
useLocalQuranSync ? .package(name: "QuranSync-lib", path: "../mobile-sync/") : .package(name: "QuranSync-lib", url: "https://github.com/quran/mobile-sync.git", from: "1.0.0"),

If that feels too awkward, the other solution I'd think of to setenv from quran-ios's Package.swift. This will be inputted to quran-ios's package by reading it from a side file or editting it manually.

@mohamede1945 Let me know what you think.

Choose a reason for hiding this comment

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

That sounds good to me. Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants