Saturday, October 14, 2023
HomeiOS DevelopmentIncluding Swift Package deal Supervisor Assist – Half 2

Including Swift Package deal Supervisor Assist – Half 2


Within the earlier submit I checked out a number of the historical past of how we packaged up our library code to be used by our fellow builders. We checked out a number of the advantages of static libraries versus dynamic frameworks which additionally include headers wanted by the integrator.

Now let’s dive into the steps that have been essential for me to allow SPM help on the primary few libraries DTCoreText, DTFoundation and Kvitto. It took me a number of days to iron out all of the kinks and I’d like to share with you what I realized within the course of.

We’re used to utilizing Xcode to explain what goes right into a construct: Which recordsdata to compile, what exterior libraries to hyperlink to, what assets are wanted and in addition common construct settings just like the vary and sorts of supported platforms. Extra exactly, these settings are contained within the venture.pbxproj file inside your xcodeproj bundle.

With SwiftPM there isn’t a such venture file. Fairly all the things is outlined in human-readable type within the Package deal.swift file.

For some fundamental terminology: we outline sure merchandise (i.e. static library, dynamic framework, app bundle and so on, useful resource bundle, unit take a look at bundle), that relate to plenty of targets (a bucket for a bunch of supply code recordsdata and assets). Here’s a distinction from Xcode the place goal and product is used synonymously.

Package deal Definition

Step one, and most necessary one, is so as to add a bundle definition file to the foundation folder of the repository. It must be on this place as a result of Swift Packages are referenced by the repository URL and SwiftPM will solely take a look at the highest folder for Package deal.swift.

Right here’s the definition for Kvitto, for reference. This has all components you may encounter, together with a dependency on one other bundle, a few assets on prime of the definition of 1 product and a number of goal.

// swift-tools-version:5.3

import PackageDescription

let bundle = Package deal(
    identify: "Kvitto",
    platforms: [
        .iOS(.v9),         //.v8 - .v13
        .macOS(.v10_10),    //.v10_10 - .v10_15
        .tvOS(.v9),        //.v9 - .v13
    ],
    merchandise: [
        .library(
            name: "Kvitto",
            targets: ["Kvitto"]),
    ],
    dependencies: [
        .package(url: "https://github.com/Cocoanetics/DTFoundation.git", 
		from: "1.7.15"),
    ],
    targets: [
        .target(
            name: "Kvitto",
            dependencies: [
                .product(name: "DTFoundation", 
				package: "DTFoundation"),
            ],
            path: "Core",
            exclude: ["Info.plist"]),
        .testTarget(
            identify: "KvittoTests",
            dependencies: ["Kvitto"],
            path: "Check",
            exclude: ["Info.plist"],
            assets: [.copy("Resources/receipt"),
                        .copy("Resources/sandboxReceipt")]),
    ]
)

The primary line may solely appear like a remark to you, however it can be crucial for the swift instruments to find out what syntax components are supported. Model 5.3 is required in case you have assets in any goal. Should you set that to one thing decrease you get syntax errors relating to the useful resource definitions. Should you set that to five.3 however don’t specify useful resource definitions (for non-standard assets) you’re going to get warnings about unknown recordsdata that it is best to both exclude or outline as assets.

I discovered myself conflicted about that, as I had talked about within the earlier article. All code would work on Swift 5.0 and up and solely the take a look at goal has assets. I may get extra inexperienced checkmarks on Swift Package deal Index if I eliminated the .testTarget definition.

On the opposite facet the swift instruments allow you to run thusly outlined unit checks from the command line and functioning unit checks additionally ought to depend as an indication of fine library high quality. Lastly, everyone ought to be utilizing Swift 5.3 anyway as that’s the baseline commonplace because the launch of Xcode 12.

That’s why I selected to depart it at that.

The fundamental setup of the bundle definition is easy. You may have the bundle identify, then some minimal platform variations. Word that these minimal OS variations don’t imply that that might limit the the bundle to particular platforms.

The merchandise part defines what sort of library comes out of the construct course of. The default setting (invisible) is to supply a static library, by specifying kind: .dynamic you get a dynamic framework as an alternative. The targets array specifies which targets will get merged into the ultimate product.

I assumed for a second that that could be good to have the assets be added to the framework as an alternative of a separate useful resource bundle, like we’re used to. However alas the dealing with of assets stays the identical they usually get bundled right into a Product_Target.bundle. So due to this fact I’d moderately have the static library – which can get merged into the app binary – moderately than having yet one more separate framework bundle contained in the app bundle.

As I defined within the earlier article, dynamic frameworks ought to be averted if the supply code for libraries is public. So we’re pleased with the static library default.

The dependencies part lists the exterior reference to different packages. You specify the repository URL and the minimal variations. The proven method with from and a model would settle for all 1.x.x variations from and together with 1.7.15. There are additionally different methods to specify a precise quantity or sure ranges.

Final come the targets. We have now a daily goal for the bundle and a take a look at goal for all of the unit checks. Should you don’t specify a path then SwiftPM expects the supply code within the Sources folder beneath the goal’s folder and assets in a Sources folder. I’ve a distinct construction, so I specified a customized path.

I’ve to exclude the Information.plist for each targets as a result of that is utilized by two targets outlined contained in the Xcode venture. And for the take a look at goal I specify two assets to be copied with the trail relative to the goal customized path. These copy directions are essential as a result of the contained assets don’t have a sort that Xcode is aware of how you can deal with. For issues like strings recordsdata or XIBs you don’t should specify something.

Evaluate the dependencies key of each targets. On the one hand you see that I’m referencing the exterior dependency of the primary goal. Alternatively the take a look at goal requires the primary goal to work. That’s additionally a distinction to Xcode the place the examined code resides inside a number software, the place’s right here it’s compiled into the unit take a look at bundle.

Goal Concerns

You could be questioning why there’s a distinction between merchandise and targets in SPM. One cause for that you’ve already seen: there isn’t a cause for the take a look at goal to be represented in a product. Easy packages will typically solely have one product that may solely consist of 1 goal.

Though I already discovered two extra causes, to separate code out into extra particular person targets after which additionally merchandise.

You may assume that Swift Package deal Supervisor would solely all you to have code written in Swift. However you’ll be improper, Any language goes, additionally Goal-C and different C dialects. However SPM doesn’t let you combine C-based languages with Swift in a single goal.

In a single venture I had some Goal-C code for a operate with plenty of ifs. I rewrote that in Swift solely to search out that compiling this might take greater than a minute, in contrast to some seconds in Goal-C. So I selected to depart the operate because it was. The answer was to place it right into a separate Goal-C goal and refer that to an inner dependency from the primary Swift goal.

The opposite good cause for a separate goal and product was to have some widespread information mannequin code that might be utilized by inner targets and in addition through import in an app consuming my library. In locations the place the shopper would solely want the shared definitions he would import the precise module for that. Elsewhere he would import different targets which in flip may additionally make use of these definitions internally.

Every product turns into its personal module.

Resourcefulness

I discussed above that you could let SPM do its personal factor with regards to commonplace useful resource varieties, like localised strings, XIBs, storyboards and asset catalogs. Should you use string localisation although, it’s a must to specify the venture’s default language.

Different varieties it’s a must to both particularly exclude or specify what ought to be achieved for it. You may both specify a .copy for every particular person useful resource or additionally for all the Sources folder. Since I’ve solely two take a look at recordsdata and that’s not going to alter, it wasn’t an excessive amount of work so as to add these individually.

SPM expects assets in the identical folder {that a} goal’s supply recordsdata reside in (or a sub-folder thereof). The rationale for that’s once more that there isn’t a Xcode venture file the place you would specify membership of sure recordsdata to particular targets. You specify what belongs the place by how it’s specified by the file system together of the bundle definition.

Say you might have a single place the place you might have localised strings recordsdata downloaded from a translation web site like POEditor however you need them to be included in several targets. A method to attain that’s to create soft-links contained in the goal’s useful resource folders to the recordsdata. I wrote this shell script to create the lproj folders for all languages after which create the hyperlinks.

#!/bin/sh

echo "Eradicating present strings"
rm -rf ../TFMViews/Sources/*.lproj
rm -rf ../TFMExtension/Sources/*.lproj

PWD=`pwd`

for entry in *.lproj
do
  echo "Linking $entry..."

  mkdir ../TFMViews/Sources/$entry
  ln -s ../../../Strings/$entry/TFMViews.stringsdict 
     ../TFMViews/Sources/$entry
  ln -s ../../../Strings/$entry/TFMViews.strings 
     ../TFMViews/Sources/$entry

  mkdir ../TFMExtension/Sources/$entry
  ln -s ../../../Strings/$entry/TFMExtension.stringsdict 
     ../TFMExtension/Sources/$entry
  ln -s ../../../Strings/$entry/TFMExtension.strings 
     ../TFMExtension/Sources/$entry

achieved

The identical strategy of soft-links can be employed for Goal-C primarily based packages the place you’ll be able to hyperlink to all related public headers in an embody folder.

Platform-specific Code

Because the bundle has no facility for limiting particular supply code to particular platforms or OS variations, you’ll face the scenario that sure code received’t compile for different platforms. A workaround for this limitation is using conditional compilation directives.

For instance, all the things that references UIKit can’t be compiled for macOS or watchOS, so I’ve a number of locations in DTCoreText or DTFoundation (each written in Goal-C) the place all the implementation is enclosed in:

#import <TargetConditionals.h>

#if TARGET_OS_IPHONE && !TARGET_OS_WATCH
...
#endif

I additionally discovered that generally I needed to additionally import the TargetConditionals header for the defines to work. Specifically sure Goal-C class extensions in DTCoreText wouldn’t be seen within the public interface if I didn’t import this header. I’ve no clarification as to why, however including the import for the header mounted it.

Contained in the Xcode Venture

The modifications for conditional compilation apart, there may be nothing you have to change in your Xcode venture – except you wish to. The principal setup for the bundle occurs in Package deal.swift. You may construct the bundle with issuing swift construct.

I discovered it handy so as to add a reference to the bundle contained in the Xcode venture as a result of this lets you debug your code within the context of being compiled for a bundle. Should you drag any folder (containing a bundle definition) into the venture navigator pane, Xcode will add an area bundle reference for you, with an emblem of a cute little field.

In Xcode 12 there’s a bug that if you happen to do this for the venture folder itself, it appears to work, however when you shut the venture and reopen it once more, the reference turns into defunct. The best way to repair it’s to alter the reference to “Relative to Venture” and open the folder selector through the folder button and re-select the venture root folder.

This additionally creates a scheme for constructing the bundle and the bundle’s merchandise change into accessible to hyperlink/embed to your app. Package deal merchandise have an icon of a greek temple. If they’re static libraries then they’ll get merged into the app binary, dynamic frameworks shall be added to the app’s Frameworks folder.

Xcode additionally creates a scheme for the bundle, putting it in .swiftpm/xcode/xcshareddata/xcschemes/. I moved it into the shared schemes folder of the xcodeproj and renamed it to Kvitto-Package deal.xcscheme.

I had the watchOS platform builds on Swift Package deal Index fail as a result of xcodebuild insists on constructing all targets, together with the take a look at goal. This fails as a result of unit checks require XCTest which doesn’t excite for watchOS.

By offering an aptly named shared scheme it’s going to solely construct the primary goal and I achieved inexperienced checkmarks for watchOS on SPI.

Library Unit Checks

To run the unit checks contained within the take a look at goal, all you have to do is to run swift take a look at on the command line, from the repository root folder.

Results of working the Kvitto unit checks from the command line

Some magic was required to get that to work as a result of take a look at recordsdata required by the unit checks will not be bundled within the .xctest bundle. For normal packages a useful resource bundle accessor is being mechanically generated, which you should utilize with Bundle.module.

The accessor works by figuring out the trail of the executable and developing a bundle identify from names of bundle and goal. Within the case of unit checks the executable is xcrun contained within the Xcode.app bundle the place it has no likelihood of discovering the Kvitto_KittoTests.bundle.

My ugly, however useful, workaround for that is as follows:

func urlForTestResource(identify: String, ofType ext: String?) -> URL?
{
	let bundle = Bundle(for: kind(of: self))
		
	#if SWIFT_PACKAGE
		
	// there's a bug the place Bundle.module factors to the trail of xcrun contained in the Xcode.app bundle, as an alternative of the take a look at bundle
	// that aborts unit checks with message:
	//   Deadly error: couldn't load useful resource bundle: /Functions/Xcode.app/Contents/Developer/usr/bin/Kvitto_KvittoTests.bundle: file KvittoTests/resource_bundle_accessor.swift, line 7
		
	// workaround: attempt to discover the useful resource bundle on the construct path
	let buildPathURL = bundle.bundleURL.deletingLastPathComponent()
		
	guard let resourceBundle = Bundle(url: buildPathURL.appendingPathComponent("Kvitto_KvittoTests.bundle")),
	   let path = resourceBundle.path(forResource: identify, ofType: ext) else
	{
		return nil
	}
		
	return URL(fileURLWithPath: path)
		
	#else
		
	guard let path = bundle.path(forResource: identify, ofType: ext) else
	{
		return nil
	}
		
	return URL(fileURLWithPath: path)
		
	#endif
}

This depends on the truth that the useful resource bundle shall be created parallel to the xctest bundle, in the identical construct folder. The #if SWIFT_PACKAGE conditional compilation will solely be added if this code is constructed as a part of a swift bundle. With this workaround, the earlier mechanisms of working the unit take a look at scheme through Xcode continues to work.

The beauty of Swift being open supply, is that we are able to additionally examine the code for the useful resource accessor on GitHub. It seems that the talked about bug has already been addressed there. The repair was made too late to make it into Swift 5.3 in Xcode 12 however has been confirmed to be current in Xcode 12.2.

Conclusion

I discover that the evolution of Swift Package deal Supervisor as progressed sufficiently to begin including help for it to my libraries. It’s doable and advisable to take action along with different methods of integration, like Xcode subproject, Cocoapods or Carthage.

Essentially the most annoying limitation remaining is that you just can’t restrict targets to sure platforms or specify a spread of supported OS variations per goal. However these can simply be labored round with conditional compilation directives.

The standard standards partially enforced by the Swift Package deal Index coupled with the discoverability of parts additionally make it engaging for library distributors to think about supporting Swift Package deal Supervisor. Having the dependency administration taken care of by Xcode is the best characteristic of all.



Additionally revealed on Medium.


Classes: Administrative



Supply hyperlink

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments