Be taught every part in regards to the SPM structure. I will 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 in search of 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 with the intention to set up your packages? Nicely, the Package deal.swift
manifest is a wierd beast. Let me present you an fast instance of a daily package deal description file:
import PackageDescription
let package deal = Package deal(
identify: "HelloSwift",
dependencies: [
],
targets: [
.target(
name: "HelloSwift",
dependencies: []),
.testTarget(
identify: "HelloSwiftTests",
dependencies: ["HelloSwift"]),
]
)
The primary line incorporates the model data, subsequent we have now to import the PackageDescription
module which incorporates all of the required parts to correctly describe a Swift package deal. For those who run for instance swift package deal replace
all of your dependencies on this manifest file might be resolved & you need to use them inside your individual code information. ✅
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 making an attempt to duplicate this behaviour with out trying on the authentic 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 information are messy – so let’s attempt to import it someway… 🙃
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, nevertheless it was enjoyable, so I used to be trying 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 know this subject higher, so I began to learn increasingly more each about static and dynamic libraries. Lengthy story quick, you’ll be able to create a dynamic (or static) library with the next product definition:
import PackageDescription
let package deal = Package deal(
identify: "instance",
merchandise: [
.library(name: "myStaticLib", type: .static, targets: ["myStaticLib"]),
.library(identify: "myDynamicLib", sort: .dynamic, targets: ["myDynamicLib"]),
],
targets: [
.target(
name: "myStaticLib",
dependencies: []),
.goal(
identify: "myDynamicLib",
dependencies: []),
]
)
The necessary information are going to be situated contained in the .construct/debug
folder. The .swiftmodule
is principally the general public header file, this incorporates all of the out there API on your library. The .swiftdoc
file incorporates the documentation for the compiled module, and relying on the sort you will additionally get a .dylib
or a .a
file. Guess which one is which.
So I might load the .dylib
file by utilizing dlopen
& dlsym
(some @_cdecl magic concerned to get fixed names as an alternative of the “fuzzy” ones), however I used to be always receiving the identical warning over and over. The dynamic loading labored properly, 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 potential… 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” had been nonetheless embedded into it. So that is the unhappy state of an unstable ABI, I believed… anyway a minimum of I’ve realized one thing essential throughout the best way right here:
Importing Swift code into Swift!
Sure, you heard that. It is potential 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? Nicely, 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 need to append a number of compiler flags. The -I
stands for the import search path, -L
is the library search path, -l
hyperlinks the given library. Verify swiftc -h
for extra particulars and flags you will not remorse it! Voilá now you’ll be able to distribute closed supply Swift packages. At the least it was good to know the way SPM does the “trick”. 🤓
Please word that till Swift 5 & ABI stability arrives you need to use the precompiled libraries with the identical Swift model solely! So should you compile a lib with Swift 4.2, your executable additionally must be compiled with 4.2., however this can 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 verify 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 necessary factor:
/Purposes/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swiftc
--driver-mode=swift
-L /Purposes/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/pm/4_2
-lPackageDescription
-suppress-warnings
-swift-version 4.2
-I /Purposes/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/pm/4_2
-target x86_64-apple-macosx10.10
-sdk /Purposes/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 primarily based on my Package deal.swift
file!!! 🎉
How the hell are they doing this?
It seems, should you change the -fileno
parameter worth to 1 (that is the usual output) you’ll be able to 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, properly it prints out the encoded JSON illustration of the Package deal
object after the method exits. That is it, fuckn’ simple, nevertheless it took 1 extra day for me to determine this out… parenting 2 youngsters & coding is a tough mixture. 🤷♂️
For those who open the /Purposes/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/pm/4_2
folder you will see 3 acquainted information there. Precisely. I additionally appeared 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 end result by utilizing 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 package deal
command underneath the swift
command?
Swift toolchain
I simply entered swift lol
into my terminal. That is what occurred:
[email protected]~: swift lol
error: unable to invoke subcommand:
/Purposes/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift-lol
(No such file or listing)
Acquired ya! The toolchain is the important thing to every part:
- Apple is compiling the
PackageDescription
library from the Swift Package deal Supervisor and places the.swiftmodule
,.swiftdoc
,.dylib
information into the correct locations underneath Xcode’s default toolchain library path. - 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)
- The swift command tries to invoke the correct subcommand if there may be any and it is a legitimate (Swift) binary. (Tried with a shell script, it failed miserably…)
- SPM makes use of the
PackageDescription
library from the toolchain with the intention to compile & flip the manifest file into JSON output. - The remainder is historical past. 🤐
Swift can resolve subcommands from wherever “inside” the PATH
variable. You simply need to 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 by utilizing this system I defined above. So I’ve made an identical extension wired into the center of the Swift toolchain: SwiftCI. ❤️
You’ll be able to seize the proof-of-concept implementation of SwiftCI from github. After putting in it you’ll be able to create your individual CI.swift
information and run your workflows.
import CI
let buildWorkflow = Workflow(
identify: "default",
duties: [
Task(name: "HelloWorld",
url: "[email protected]:BinaryBirds/HelloWorld.git",
model: "1.0.0",
inputs: [:]),
Process(identify: "OutputGenerator",
url: "~/ci/Duties/OutputGenerator",
model: "1.0.0",
inputs: [:]),
Process(identify: "SampleTask",
url: "[email protected]:BinaryBirds/SampleTask.git",
model: "1.0.1",
inputs: ["task-input-parameter": "Hello SampleTask!"]),
])
let testWorkflow = Workflow(
identify: "linux",
duties: [
Task(name: "SampleTask",
url: "https://github.com/BinaryBirds/SampleTask.git",
version: "1.0.0",
inputs: ["task-input-parameter": "Hello SampleTask!"]),
])
let mission = Venture(identify: "Instance",
url: "[email protected]:BinaryBirds/Instance.git",
workflows: [buildWorkflow, testWorkflow])
The code above is a pattern from a CI.swift
file, you’ll be able to merely run any workflow with the swift ci run workflow-name
command. Every little thing 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 end result, not simply due to the ultimate product (that is solely a proof of idea implementation), however largely due to the issues I’ve realized through the creation course of. If you wish to get extra suggestions and subjects like this you need to comply with me on Twitter and subscribe to my month-to-month publication.