Skip to content

SPM Multi-module support - #291

Open
faogustavo wants to merge 6 commits into
mainfrom
spm-multi-module
Open

faogustavo wants to merge 6 commits into
mainfrom
spm-multi-module

Conversation

@faogustavo

Copy link
Copy Markdown
Contributor

hanrw and others added 6 commits September 14, 2026 15:58
Add a new root-level plugin `co.touchlab.kmmbridge.spm` that automatically
generates Package.swift for multi-module KMP projects.

## New Features

- **spmDevBuildAll**: Builds all XCFrameworks locally and generates
  Package.swift with local paths for development
- **kmmBridgePublishAll**: Publishes all modules and generates
  Package.swift with URLs for distribution
- **generatePackageSwift**: Generates Package.swift from published metadata

## Changes

- Add `KmmBridgeSpmPlugin` for root-level SPM management
- Add `KmmBridgeSpmExtension` for configuration options
- Add `SpmModuleMetadata` for JSON metadata exchange between modules
- Add `writeSpmMetadata` task to each module for metadata generation
- Disable module-level `spmDevBuild` when root SPM plugin is applied
- Add comprehensive documentation in docs/SPM_MULTI_MODULE.md

## Benefits

- No manual Package.swift editing required
- No need for `useCustomPackageFile` or `perModuleVariablesBlock` flags
- Unified workflow for both local development and CI publishing
- Automatic platform version resolution (takes maximum)
- Automatic Swift tools version resolution
- Address Copilot review feedback for SPM multi-module support
- Move Package.swift content generation logic to a new class for testability
- Simplify KmmBridgeSpmPlugin by delegating package generation
- Enhance platform version parsing in SpmDependencyManager with max-version logic
- Remove obsolete local Package.swift generation code, use centralized generator class
Fix broken swiftToolVersion example and document behavior added since
the doc was first written: kmmBridgePublishAll's dependsOn-based
atomicity, generatePackageSwift's fail-fast on partial metadata,
per-module updatePackageSwift/spmDevBuild auto-skip under the root
aggregator, the framework-name uniqueness requirement, and that
Swift-tools-version max-resolution applies to both the remote and
local-dev generation paths.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

One or more issues must be addressed before approval.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds root-level multi-module SPM support for KMMBridge, including automatic local and published Package.swift generation.

Changes:

  • Adds SPM aggregation plugin, extension, registry, metadata, and generation tasks.
  • Adds multi-module integration fixtures and tests.
  • Adds documentation and Gradle plugin version-alias updates.
File summaries
File Description
test-projects/multi-module/settings.gradle.kts Updated as part of this pull request.
test-projects/multi-module/module-excluded/src/iosMain/kotlin/co/touchlab/kmmbridgetest/moduleexcluded/StartSDK.kt Updated as part of this pull request.
test-projects/multi-module/module-excluded/build.gradle.kts Updated as part of this pull request.
test-projects/multi-module/module-b/src/iosMain/kotlin/co/touchlab/kmmbridgetest/moduleb/StartSDK.kt Updated as part of this pull request.
test-projects/multi-module/module-b/build.gradle.kts Updated as part of this pull request.
test-projects/multi-module/module-a/src/iosMain/kotlin/co/touchlab/kmmbridgetest/modulea/StartSDK.kt Updated as part of this pull request.
test-projects/multi-module/module-a/build.gradle.kts Updated as part of this pull request.
test-projects/multi-module/LICENSE.txt Updated as part of this pull request.
test-projects/multi-module/gradlew.bat Updated as part of this pull request.
test-projects/multi-module/gradlew Updated as part of this pull request.
test-projects/multi-module/gradle/wrapper/gradle-wrapper.properties Updated as part of this pull request.
test-projects/multi-module/gradle/libs.versions.toml Updated as part of this pull request.
test-projects/multi-module/gradle.properties Updated as part of this pull request.
test-projects/multi-module/build.gradle.kts Updated as part of this pull request.
test-projects/basic/allshared/src/iosMain/kotlin/co.touchlab/kmmbridgetest/StartSDK.kt Updated as part of this pull request.
test-projects/basic/allshared/build.gradle.kts Updated as part of this pull request.
kmmbridge/src/test/kotlin/co/touchlab/kmmbridge/spm/SpmModuleMetadataTest.kt Updated as part of this pull request.
kmmbridge/src/test/kotlin/co/touchlab/kmmbridge/spm/PackageSwiftGeneratorTest.kt Updated as part of this pull request.
kmmbridge/src/test/kotlin/co/touchlab/kmmbridge/spm/KmmBridgeSpmMultiModuleTest.kt Updated as part of this pull request.
kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/spm/SpmModuleMetadata.kt Updated as part of this pull request.
kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/spm/PackageSwiftGenerator.kt Updated as part of this pull request.
kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/spm/KmmBridgeSpmRegistry.kt Updated as part of this pull request.
kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/spm/KmmBridgeSpmPlugin.kt Updated as part of this pull request.
kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/spm/KmmBridgeSpmExtension.kt Updated as part of this pull request.
kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/KMMBridge.kt Updated as part of this pull request.
kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/internal/ProjectExtensionsInternal.kt Updated as part of this pull request.
kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/dependencymanager/SpmDependencyManager.kt Updated as part of this pull request.
kmmbridge/build.gradle.kts Updated as part of this pull request.
kmmbridge-test/build.gradle.kts Updated as part of this pull request.
kmmbridge-gitlab/build.gradle.kts Updated as part of this pull request.
kmmbridge-github/build.gradle.kts Updated as part of this pull request.
gradle/libs.versions.toml Updated as part of this pull request.
docs/SPM_MULTI_MODULE.md Updated as part of this pull request.
build.gradle.kts Updated as part of this pull request.
Review details

Suppressed comments (5)

docs/SPM_MULTI_MODULE.md:82

  • spmDevBuildAll depends directly on each module's debug XCFramework assemble task (KmmBridgeSpmPlugin.kt:190-191), not on the module spmDevBuild tasks. Therefore those per-module tasks do not run and the claimed Skipping spmDevBuild messages will not appear during spmDevBuildAll; distinguish the root task from an explicit per-module invocation, or change the task graph if that behavior is intended.
> **Note**: When the root SPM plugin is applied, each module's own `spmDevBuild` and `updatePackageSwift`
> tasks still run (and can still be up-to-date-checked), but their write step short-circuits at
> execution time - each logs a `Skipping ...` message and defers to `spmDevBuildAll`/the root
> `generatePackageSwift` instead of writing its own single-module Package.swift and racing with the
> aggregated one. This is expected; you'll see these log lines for every module when running

kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/dependencymanager/SpmDependencyManager.kt:393

  • parsePlatformsMap documents that it keeps the maximum when multiple DSL entries map to one platform, but this comparison only parses whole-number strings and otherwise compares lexicographically. Valid dotted versions such as 10.15 and 9.0 therefore select 9.0 as the maximum, producing an incorrect platform requirement. Compare dot-separated numeric components, consistent with the comparator used by PackageSwiftGenerator.
        return if (candidateInt != null && currentInt != null) {
            candidateInt > currentInt
        } else {
            candidate > current

kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/spm/KmmBridgeSpmPlugin.kt:165

  • modulesProvider contains modules registered by configureLocalDev even when ENABLE_PUBLISHING is false, but BaseKMMBridgePlugin only creates each module's kmmBridgePublish task inside the publishing branch. Therefore the documented ./gradlew kmmBridgePublishAll invocation resolves dependencies on missing :module:kmmBridgePublish tasks instead of warning/skipping; filter this task to publish-enabled registrations or fail early with the required configuration.
            // Depend on all module kmmBridgePublish tasks, resolved lazily by task path. Modules
            // only register themselves once publishing is configured (ENABLE_PUBLISHING), so a
            // module without publishing enabled simply never appears here.
            dependsOn(modulesProvider.map { modules -> modules.map { "${it.path}:${BaseKMMBridgePlugin.PUBLISH_TASK_NAME}" } })

kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/spm/KmmBridgeSpmPlugin.kt:63

  • Applying this plugin to a subproject only logs a warning, but still marks the shared registry as root-applied and registers the aggregate tasks on that subproject. That disables every module's per-module SPM writers without creating the aggregate tasks in the actual root build, so this misconfiguration should fail fast (or return before marking the registry).
        if (project != project.rootProject) {
            logger.warn("KmmBridgeSpmPlugin should be applied to the root project only")
        }

kmmbridge/src/main/kotlin/co/touchlab/kmmbridge/spm/KmmBridgeSpmPlugin.kt:195

  • This repeats the eager .get() problem for spmDevBuildAll: user-supplied packageName, outputDirectory, or swiftToolsVersion values configured after plugin application are ignored, so local generation writes the default Package.swift location/name and tools version instead of the configured ones. Use the extension properties as lazy providers through task registration and execution.
            val outputDir = extension.outputDirectory.get()
            val packageName = extension.packageName.get()
            val defaultSwiftToolsVersion = extension.swiftToolsVersion.get()
  • Files reviewed: 32/35 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +98 to +99
// Depend on all module metadata-write tasks, resolved lazily by task path
dependsOn(modulesProvider.map { modules -> modules.map { "${it.path}:$WRITE_SPM_METADATA_TASK_NAME" } })
Comment on lines +101 to +103
val outputDir = extension.outputDirectory.get()
val packageName = extension.packageName.get()
val swiftToolsVersion = extension.swiftToolsVersion.get()
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.

3 participants