How to generate a framework from a SwiftPM-generated XCode project? - swift

I used SwiftPM to set up an XCode project for a framework; based on
// swift-tools-version:4.0
import PackageDescription
let package = Package(
name: "MyThing",
products: [
.library(
name: "MyThing",
targets: ["MyThing"]),
],
dependencies: [
],
targets: [
.target(
name: "MyThing",
dependencies: [])
)
I ran swift package generate-xcodeproj and then pod install (based on a Podfile whose content shouldn't matter). I obtain MyThing.xcworkspace.
Now I figured that
xcodebuild -workspace MyThing.xcworkspace -scheme MyThing clean build
should create the .framework -- but it doesn't, only a binary file appears. I suspect some automatism is at work here since the source folder contains a file named main.swift, among others.
What do I have to do to get the framework built?
I need to script the whole process, to please no manual workarounds.

As of Swift 4.0 swift package generate-xcodeproj doesn't automatically generate schemes for all targets, but still makes those targets accessible from Xcode.
If creating a new scheme manually once is acceptable, you can do so and add your framework target as a scheme build target.
Otherwise, new schemes can be created programmatically with libraries like xcodeswift/xcproj, which allow you to parse a newly generated Xcode project and generate a new scheme with its XCSharedData class.
After that new framework scheme is created you can run the build with xcodebuild -workspace MyThing.xcworkspace -scheme MyThingFramework clean build.

Mixing script files and framework code in one target seems to confuse SwiftPM. Moving main.swift out of the source folder clears up things.
Scheme MyThing-Package creates all build products as specified in Package.swift, including the framework.
If you want to have the script files in the same XCode Workspace (for editing convenience), put them in their own target. You can even create an executable product (which won't create anything useful since an executable can't link to dynamic frameworks).

Related

How to combine .metal and .swift in the same package

I created a MacOS command line app that defines and successfully calls a Metal kernel. I'm now trying to move this app's .metal and .swift logic into its own package so that it can used in other projects. I expected I would be able to create a Swift package, add my .metal and .swift logic and build/test it with no issues but this has not been the case.
In Xcode I created a new package (File->New->Package). After the package was created I tried to add a Metal file (right-click on sources -> New File). I opened the macOS tab in the window and tried to find a .metal template file but there was no result. I then just tried copy/pasting my .metal file into the sources and that worked but when I went to build I got an error
CompileSwiftSources normal x86_64 com.apple.xcode.tools.swift.compiler (in target '...' from project '...')
...
swiftc -incremental -module-name ...
...
Command CompileSwiftSources failed with a nonzero exit code
How can I add and build .metal files as part of my Xcode Swift package? I can't seem to find what I'm missing. I've found examples of packages on GitHub that have a combination of Metal and Swift in them and I managed to get this one (Metal in sources/other) to compile on my computer in Xcode.
Have you updated your Swift package file?
.metallib file (Bundle.module) is only available once you have specified your resources in the package manifest.
Package.swift:
.target(
name: "TargetName",
dependencies: [],
resources: [.process("ThePathToYour/Shader.metal")]
)

No such module in a Swift Package using Xcode - package listed in dependencies

I create a blank template package:
> swift package init --name Temp
> open Package.swift
Xcode Version 13.2.1 (13C100) opens the package.
I add a dependency to the package.
dependencies: [
.package(url: "https://github.com/johnsundell/publish.git", from: "0.7.0")
],
Xcode > Product > Build succeeds at this point.
I edit Temp/Sources/Temp/Temp.swift to insert the first line the package that is defined in dependencies.
import Publish
A build now generates the following error:…/Temp/Sources/Temp/Temp.swift:1:8: error: no such module 'Publish'.
I feel certain this is an Apple bug. Or I could be missing something.
There are several posts about this problem when there is an xcodeproj and the additional structure that provides. Some of them hint at workarounds that help some people.
Has anyone seen this and/or know of how to resolve it?
Apple's Creating a Standalone Swift Package with Xcode document doesn't provide any insight.
thanks for the chatter in the comments, #Larme & #koen, it helped
The issue was user error (and/or a documentation lapse). Living on the (bleeding) edge.
Sometimes updates from changes are slow or require a clean or a relaunch.
Xcode auto-generates Schemes from the targets defined in your package. My build was targeting MyTarget.
Two things were missing:
name: "Publish" was not included in the package dependency - it's needed so you can reference it below (or maybe this can be derived, it's hard to tell because of Xcode refresh issues), and
a reference is needed in the dependencies for each target using the package-dependency, i needed to add dependencies: ["Publish"] in the related target
dependencies: [
.package(name: "Publish", url: "https://github.com/johnsundell/publish.git", from: "0.7.0")
],
…
targets: [
.target(
name: "MyTarget",
dependencies: ["Publish"]),
]

Exporting Package.swift for Swift Package Manager from existing Xcode project

I'm working with Xcode 11.3 on macOS Catalina 10.15.6.
I have an existing Xcode project which builds an app for iOS. I am interested in reusing some of the classes in an interactive session with the swift command line interpreter. The classes I want to work with are Core Data classes which are autogenerated from an Xcode data model and also some classes I've written which work with the Core Data classes. The app has UI screens and makes use of UIKit but I'm not trying to use any of those classes; I'm hoping that I can either compile those classes and then not refer to them, or somehow tell Swift Package Manager to ignore those classes altogether.
What I think I would like to do is to export a Package.swift for the existing Xcode project, such that swift build at the command line would be able to compile all of the project classes, or, failing that, at least the non-UI classes, and then swift run --repl would be able to load the classes via import.
I see a menu item in Xcode to create a new Swift package, but not to export an existing project. Is there a way to export an existing project?
There are no a menu command or utility to convert application to a static library, dynamic framework or swift package since they are different types of projects with different settings etc.
If you want to export a part of your project as a swift package you should make next steps manually:
1. Create Package.swift file in the root of your project
import PackageDescription
let package = Package(
name: “MyLib”,
products: [
.library(name: "MyLib", targets: ["MyLib"])
],
targets: [
.target(name: "MyLib"),
],
...
)
2. Make folder with subfolder ./Sources/MyLib under the projects’s root.
By default swift package structure requires to put all your sources files under Sources/LibraryName folder but you can change it below.
NOTE: you can simplify first two steps by using swift package init and it creates Package.swift, Sources and Test folders etc.
3. Include source files
a) Move the needed files to share from their current locations to MyLib folder.
For instance:
./Classes/MyEntity.swift -> ./Sources/MyLib/MyEntity.swift
Also you have to update locations of the moved files in your Xcode project to leave it compilable.
b) Use path, sources and exclude to point needed source files to your package from their current locations:
.target(name: "MyLib", path: "Classes"),
NOTE: Don't forget to make your classes public to access to them after import your package:
public class MyEntity {
...
}
After all you will have two working projects - old XCode's one and new Swift package.
4. REPL
Now you can use command line interpreter with your swift package:
swift run --repl
import MyLib
let entity = MyEntity()
...

Package Loading: "Ignoring duplicate product" (SwiftPM)

I'm trying to build a Swift package using the Swift Package Manager.
However, when I open the package in Xcode and click Run, I get the following warning:
How can I resolve this?
This is an issue often encountered if you are attempting to build a library/framework but also have a main.swift file in your target's root directory, like so:
.
The presence of a main.swift file makes SwiftPM believe that you are attempting to build a command line tool, and thus complains about the unnecessary (duplicate in name) library produced.
This warning may be confusing as it does not occur in projects initialized with the dynamic framework template from Xcode, for iOS, macOS, tvOS or watchOS.
In my case it was because I had the same name in both the products section and the Package e.g.
let package = Package(
name: "Example",
products: [
.library(
name: "Example",
targets: ["Example"]),
],
)
This seems to be the default behavior of swift init for some reason. I removed the .library entry and the warning went away.

Importing modules with Swift package manager

I am trying to use Swift's package manager to import external modules in my project. My first module come from the Vapor project. I cannot seem to get it working. I start with
swift package init
swift package generate-xcodeproj
My Package.swift looks like this:
import PackageDescription
let package = Package(
name: "OpenTools",
products: [
.library(
name: "OpenTools",
targets: ["OpenTools"]),
],
dependencies: [
.package(url: "https://github.com/vapor/json.git", from: "2.0.0")
],
targets: [
.target(name: "OpenTools", dependencies: ["JSON"]),
]
)
I then run
swift package update
swift package generate-xcodeproj # to regenerate with dependencies
and then try to import the JSON package in my main file
import JSON
The modules are there as shown below but the import gets back with an No such module 'JSON' error.
Any thoughts?
Probably the problem lies within Xcode, as it does not know yet that JSON exists, because it was not built yet. This can easily be solved by just building your project (with cmd-B). With the generated xcodeproj, Xcode should know that it first needs to build JSON and then the rest, because JSON is marked as a dependency for your target.
You can check this, by navigating in Xcode to your target (when you click on the project description file) and afterwards to "Build Phases". Under Target Dependencies you should find your JSON module.
In addition you should find a JSON module under your targets, which compiles the sources you gathered from github.
Your project should also build when executing swift build in your project root.
With Xcode 11 you should be able to open Package.swift directly which will give you a proving ground for verifying the package manifest (aka: the Package.swift file) and compiling the target. This should help see what is actually causing the error that's preventing the module from being compiled.