Tuesday, June 16, 2026
HomeiOS DevelopmentAll in regards to the Swift Package deal Supervisor and the Swift...

All in regards to the Swift Package deal Supervisor and the Swift toolchain

[ad_1]

Study every part in regards to the SPM structure. I am going to additionally train you combine your binary executable into the Swift toolchain.

Swift

If you do not know an excessive amount of in regards to the Swift Package deal Supervisor, however you’re on the lookout for the fundamentals please learn my tutorial about SPM that explains just about every part. The purpose of this text is to go deep into the SPM structure, additionally earlier than you begin studying this I might advocate to additionally learn my article about frameworks and instruments. 📖

Prepared? Go! I imply Swift! 😂


Swift Package deal Supervisor

Have you ever ever questioned about how does SPM parse it is manifest file as a way to set up your packages? Properly, the Package deal.swift manifest is an odd beast. Let me present you an fast instance of a daily bundle description file:


import PackageDescription

let bundle = Package deal(
    title: "HelloSwift",
    dependencies: [
        
    ],
    targets: [
        .target(
            name: "HelloSwift",
            dependencies: []),
        .testTarget(
            title: "HelloSwiftTests",
            dependencies: ["HelloSwift"]),
    ]
)

The primary line accommodates the model data, subsequent we’ve got to import the PackageDescription module which accommodates all of the required parts to correctly describe a Swift bundle. When you run for instance swift bundle replace all of your dependencies on this manifest file will likely be resolved & you should use them inside your individual code recordsdata. ✅

However how on earth are they doing this magic? 💫

That query was bugging me for some time, so I did a little analysis. First I used to be attempting to copy this behaviour with out wanting on the unique implementation of the Swift Package deal Supervisor at github. I knew I shoudn’t parse the Swift file, as a result of that’d be a horrible factor to do – Swift recordsdata are messy – so let’s attempt to import it by some means… 🙃

Dynamic library loading strategy

I looked for the “dynamic swift library” key phrases and located an attention-grabbing discussion board subject on swift.org. Yeah, I am making some progress I believed. WRONG! I used to be method farther from the precise answer than I although, however it was enjoyable, so I used to be wanting into the implementation particulars of open a compiled .dylib file utilizing dlopen & dlsym from Swift. How does one create a .dylib file? Ah, I already know this! 👍

I at all times wished to grasp this subject higher, so I began to learn increasingly more each about static and dynamic libraries. Lengthy story quick, you possibly can create a dynamic (or static) library with the next product definition:


import PackageDescription

let bundle = Package deal(
    title: "instance",
    merchandise: [
        .library(name: "myStaticLib", type: .static, targets: ["myStaticLib"]),
        .library(title: "myDynamicLib", sort: .dynamic, targets: ["myDynamicLib"]),
    ],
    targets: [
        .target(
            name: "myStaticLib",
            dependencies: []),
        .goal(
            title: "myDynamicLib",
            dependencies: []),
    ]
)

The vital recordsdata are going to be situated contained in the .construct/debug folder. The .swiftmodule is mainly the general public header file, this accommodates all of the obtainable API in your library. The .swiftdoc file accommodates the documentation for the compiled module, and relying on the kind you may additionally get a .dylib or a .a file. Guess which one is which.

So I may load the .dylib file through the use of dlopen & dlsym (some @_cdecl magic concerned to get fixed names as a substitute of the “fuzzy” ones), however I used to be continuously receiving the identical warning over and over. The dynamic loading labored effectively, however I wished to eliminate the warning, so I attempted to take away the embedded the lib dependency from my executable goal. (Trace: not likely doable… afaik. anybody? 🙄)

I used to be messing round with rpaths & the install_name_tool for like hours, however even after I succesfully eliminated my library from the executable, “libSwift*issues” have been nonetheless embedded into it. So that is the unhappy state of an unstable ABI, I believed… anyway no less than I’ve realized one thing essential throughout the way in which right here:

Importing Swift code into Swift!

Sure, you heard that. It is doable to import compiled Swift libraries into Swift, however not lots of people heard about this (I assume). It is not a well-liked subject amongs iOS / UIKit builders, however SPM does this on a regular basis behind the scenes. 😅

How on earth can we import the pre-built libraries? Properly, it is fairly easy.



swiftc dynamic_main.swift -I ./.construct/debug -lmyDynamicLib -L ./.construct/debug
swiftc static_main.swift -I ./.construct/debug -lmyStaticLib -L ./.construct/debug



swift construct -Xswiftc -I -Xswiftc ./.construct/debug -Xswiftc -L -Xswiftc ./.construct/debug -Xswiftc -lmyStaticLib
swift construct -Xswiftc -I -Xswiftc ./.construct/debug -Xswiftc -L -Xswiftc ./.construct/debug -Xswiftc -lmyDynamicLib

You simply must append just a few compiler flags. The -I stands for the import search path, -L is the library search path, -l hyperlinks the given library. Examine swiftc -h for extra particulars and flags you will not remorse it! Voilá now you possibly can distribute closed supply Swift packages. A minimum of it was good to know the way SPM does the “trick”. 🤓

Please observe that till Swift 5 & ABI stability arrives you should use the precompiled libraries with the identical Swift model solely! So if you happen to compile a lib with Swift 4.2, your executable additionally must be compiled with 4.2., however this may change fairly quickly. 👏

The Swift Package deal Supervisor methodology

After 2 days of analysis & studying I actually wished to unravel this, so I’ve began to test the supply code of SPM. The very first thing I’ve tried was including the --verbose flag after the swift construct command. Right here is the vital factor:

/Functions/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swiftc 
--driver-mode=swift 
-L /Functions/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/pm/4_2 
-lPackageDescription 
-suppress-warnings 
-swift-version 4.2 
-I /Functions/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/pm/4_2 
-target x86_64-apple-macosx10.10 
-sdk /Functions/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk 
/Customers/tib/instance/Package deal.swift 
-fileno 5

Whoa, this spits out a JSON based mostly on my Package deal.swift file!!! 🎉

How the hell are they doing this?

It seems, if you happen to change the -fileno parameter worth to 1 (that is the usual output) you possibly can see the outcomes of this command on the console. Now the trick right here is that SPM merely compiles the Package deal.swift and if there’s a -fileno flag current within the command line arguments, effectively it prints out the encoded JSON illustration of the Package deal object after the method exits. That is it, fuckn’ straightforward, however it took 1 extra day for me to determine this out… parenting 2 youngsters & coding is a tough mixture. 🤷‍♂️

When you open the /Functions/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/pm/4_2 folder you may see 3 acquainted recordsdata there. Precisely. I additionally regarded on the supply of the Package deal.swift file from the SPM repository, and adopted the registerExitHandler methodology. After a profitable Package deal initialization it merely registers an exit handler if a -fileno argument is current encodes itself & dumps the outcome through the use of the file handler quantity. Candy! 😎

Since I used to be just about within the end lap, I wished to determine yet another factor: how did they handle to place the swift bundle command underneath the swift command?


Swift toolchain

I simply entered swift lol into my terminal. That is what occurred:

tib@~: swift lol
error: unable to invoke subcommand:
/Functions/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift-lol
(No such file or listing)

Obtained ya! The toolchain is the important thing to every part:

  1. Apple is compiling the PackageDescription library from the Swift Package deal Supervisor and places the .swiftmodule, .swiftdoc, .dylib recordsdata into the correct locations underneath Xcode’s default toolchain library path.
  2. The swift construct, run, check subcommands are simply one other Swift binary executables positioned contained in the toolchain’s binary path. (Named like: swift-package, swift-build, swift-run, swift-test)
  3. The swift command tries to invoke the correct subcommand if there’s any and it is a legitimate (Swift) binary. (Tried with a shell script, it failed miserably…)
  4. SPM makes use of the PackageDescription library from the toolchain as a way to compile & flip the manifest file into JSON output.
  5. The remainder is historical past. 🤐

Swift can resolve subcommands from wherever “inside” the PATH variable. You simply must prefix your Swift script with swift- and also you’re good to go.


SwiftCI – a job runner for Swift

I had this concept that it would be good to have a grunt / gulp like job runner additionally a steady integration sercie on a long run through the use of this method I defined above. So I’ve made an analogous extension wired into the center of the Swift toolchain: SwiftCI. ❤️

You may seize the proof-of-concept implementation of SwiftCI from github. After putting in it you possibly can create your individual CI.swift recordsdata and run your workflows.

import CI

let buildWorkflow = Workflow(
    title: "default",
    duties: [
        Task(name: "HelloWorld",
             url: "git@github.com:BinaryBirds/HelloWorld.git",
             version: "1.0.0",
             inputs: [:]),

        Activity(title: "OutputGenerator",
             url: "~/ci/Duties/OutputGenerator",
             model: "1.0.0",
             inputs: [:]),

        Activity(title: "SampleTask",
             url: "git@github.com:BinaryBirds/SampleTask.git",
             model: "1.0.1",
             inputs: ["task-input-parameter": "Hello SampleTask!"]),
    ])

let testWorkflow = Workflow(
    title: "linux",
    duties: [
        Task(name: "SampleTask",
             url: "https://github.com/BinaryBirds/SampleTask.git",
             version: "1.0.0",
             inputs: ["task-input-parameter": "Hello SampleTask!"]),
        ])

let mission = Undertaking(title: "Instance",
                      url: "git@github.com:BinaryBirds/Instance.git",
                      workflows: [buildWorkflow, testWorkflow])

The code above is a pattern from a CI.swift file, you possibly can merely run any workflow with the swift ci run workflow-name command. Every part is 100% written in Swift, even the CI workflow descriptor file. I am planning to increase my ci namespace with some useful subcommands in a while. PR’s are greater than welcomed!

I am very pleased with the outcome, not simply due to the ultimate product (that is solely a proof of idea implementation), however largely due to the issues I’ve realized throughout the creation course of. If you wish to get extra suggestions and matters like this it’s best to observe me on Twitter and subscribe to my month-to-month e-newsletter.



[ad_2]

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments