Swift Package Manager supports Git Submodules

Swift Package Manager supports Git Submodules

Swift Package Manager allows you to define Dependencies to other packages.

import PackageDescription

let package = Package(
    name: "MyPackage",
    dependencies: [
        .package(url: "https://github.com/apple/example-package-playingcard.git", from: "3.0.4"),
    ],
    targets: [
        .target(
            name: "MyPackage",
            dependencies: ["PlayingCard"]
        ),
        .testTarget(
            name: "MyPackageTests",
            dependencies: ["MyPackage"]
        ),
    ]
)

Since Swift 5.3 you are also able to bundle images, data files, and other resources needed at runtime.

What about git submodules ?

You might be interested in using git submodules to share common code or resources between projects. An example is that you are building an iOS and an Android app. Both apps have different repositories, and you even modularized your iOS app with Swift packages. But in both apps you want to execute common code in a JavaScript engine. Hence you think about placing the shared JavaScript code into a git submodule.

There are misleading entries in the Swift Forums but if you dig deep enough you will find a comment about SwiftPM will automatically download submodules when fetching a Swift package’s source during the build process.

I created a repository with a git submodule, and I can confirm that SPM will initialize/update a git submodule if present in a Swift Package.

Screen Shot 2022-09-30 at 11 36 26 AM

Screen Shot 2022-09-30 at 11 38 22 AM

This is ensured by GitRepository.swift implementation in Swift Package Manager.

    public func checkout(tag: String) throws {
        // FIXME: Audit behavior with off-branch tags in remote repositories, we
        // may need to take a little more care here.
        // use barrier for write operations
        try self.lock.withLock {
            try callGit("reset", "--hard", tag,
                        failureMessage: "Couldn’t check out tag ‘\(tag)’")
            try self.updateSubmoduleAndCleanNotOnQueue()
        }
    }

    public func checkout(revision: Revision) throws {
        // FIXME: Audit behavior with off-branch tags in remote repositories, we
        // may need to take a little more care here.
        // use barrier for write operations
        try self.lock.withLock {
            try callGit("checkout", "-f", revision.identifier,
                        failureMessage: "Couldn’t check out revision ‘\(revision.identifier)’")
            try self.updateSubmoduleAndCleanNotOnQueue()
        }
    }

    /// Initializes and updates the submodules, if any, and cleans left over the files and directories using git-clean.
    private func updateSubmoduleAndCleanNotOnQueue() throws {
        try self.callGit("submodule", "update", "--init", "--recursive",
                         failureMessage: "Couldn’t update repository submodules")
        try self.callGit("clean", "-ffdx",
                         failureMessage: "Couldn’t clean repository submodules")
    }

Note: I had cases that Xcode did not show the folder with the submodule content but the content was available in the respective checkout result that is stored in the DerivedData folder (example: ~/Library/Developer/Xcode/DerivedData/AppUsingPackage-randomString/SourcePackages/checkouts/SwiftPackageWithGitSubmodule).

Did you find this article valuable?

Support Marco Eidinger by becoming a sponsor. Any amount is appreciated!