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

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()
...

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")]
)

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

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).

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.

Swift Package Manager - How to use it

I have a project that I want to use this package in my app. I googled and follow the instructions:
//in Terminal
? mkdir SGLMath
? cd SGLMath
? swift package init --type executable
Then I open the package.swift file and change to this:
// swift-tools-version:3.1
import PackageDescription
let package = Package(
name: "SGLMath",
dependencies: [
.Package(url: "https://github.com/SwiftGL/Math.git", majorVersion: 1)
]
)
then Terminal:
? swift package fetch
Then I got this error:
error: the package has an unsupported layout, unexpected source
file(s) found:
/Users/xuanxi/SGLMath/.build/checkouts/Math.git-9167533630816302265/Tests/EqualWithAccuracy.swift,
/Users/xuanxi/SGLMath/.build/checkouts/Math.git-9167533630816302265/Tests/FunctionsTests.swift,
/Users/xuanxi/SGLMath/.build/checkouts/Math.git-9167533630816302265/Tests/Matrix2x2Tests.swift,
/Users/xuanxi/SGLMath/.build/checkouts/Math.git-9167533630816302265/Tests/Matrix3x3Tests.swift,
/Users/xuanxi/SGLMath/.build/checkouts/Math.git-9167533630816302265/Tests/Matrix4x4Tests.swift,
/Users/xuanxi/SGLMath/.build/checkouts/Math.git-9167533630816302265/Tests/SwizzleTests.swift,
/Users/xuanxi/SGLMath/.build/checkouts/Math.git-9167533630816302265/Tests/Vector2Tests.swift,
/Users/xuanxi/SGLMath/.build/checkouts/Math.git-9167533630816302265/Tests/Vector4Tests.swift,
/Users/xuanxi/SGLMath/.build/checkouts/Math.git-9167533630816302265/Tests/glmMatrixTests.swift
fix: move the file(s) inside a module
How can I resolve this?
What if I want to add this package into my existing Xcode project?
The project https://github.com/SwiftGL/Math.git does not have the correct Swift Package Manager format. The files in Tests directory should be in a directory with name: the module name + "Tests" ending appended, in this case SGLMathTests. You can fork the project and fix it or ask the author to fix it.
To use the project with Xcode, once you fix it:
a. Run swift package generate-xcodeproj. It will generate an Xcode project with the package.
b. Create an Xcode workspace and add to it your existing Xcode project and the generated project in the previous step. Add the framework with the package from the generated project to your existing Xcode project as a dependency. This should work.

Where to create your own file in Vapor?

I just tried to create a web app using Vapor, but when I tried to define my own variable/constant in my project, and write it in main.swift, the following error occurred.
let websiteURL = "http://localhost:8080" // in AppConstants.swift
let url = websiteURL // in main.swift
Use of unresolved identifier: websiteURL
However, it seems that the PostController class defined in Controllers directory can be seen without any problems. I put AppConstants.swift in the same directory as main.swift, but it got the error above.
In traditional iOS/macOS app, which is the only project I have developed so far, if you just create any files in the project, they can be seen from anywhere in the project, I think.
So how and where can I create and define my custom file in Vapor?
UPDATE
Here is my Package.swift file in my project:
import PackageDescription
let package = Package(
name: "vapor_sample",
dependencies: [
.Package(url: "https://github.com/vapor/vapor.git", majorVersion: 1, minor: 0)
],
exclude: [
"Config",
"Database",
"Localization",
"Public",
"Resources",
"Tests",
]
)
UPDATE 2
I found that for some reasons, the default files created by Vapor are named with directory suffix, like Controllers/PostController.swift, not PostController.swift. I tried moving AppConstants.swift under the directory, but it doesn't show its file name with the directory suffix. Is there anything related to it? I use Xcode 8.0.
Xcode projects associated w/ SwiftPM and Server Side Swift generally tend to be a bit finicky. You should regen as often and as regularly as possible. Also cleaning and deleting derived data never hurts. Here's some situations you might want to regen:
New File
Changed File Name
New Folder
New Package
Change Package Version
Feel like it
Basically, anytime anything goes wrong in Xcode, and you don't know where to start. Clean, Delete Derived, Regen.