I added Objective-C files to Swift package via modulemap file, but implementation wasn't connected - swift

I have a Swift package, and try to add Objective-c files.
My package now:
Root
- Package.swift
+ Sources
+ ObjC
+ DataDeflate
- DataDeflate.h
- module.modulemap
- NSData+Deflate.h
- NSData+Deflate.m
+ Swift
+ DataDeflator
- DataDeflatow.swift
+ Tests
+ DataDeflatorTests
- DataDeflatorTests.swift
Package.swift content:
import PackageDescription
let package = Package(
name: "DataDeflator",
products: [
.library(name: "DataDeflator", targets: ["DataDeflator"]),
],
targets: [
.target(
name: "DataDeflator",
dependencies: ["DataDeflate"],
path: "Sources/Swift/DataDeflator"),
.systemLibrary(name: "DataDeflate", path: "Sources/ObjC/DataDeflate"),
.testTarget(
name: "DataDeflatorTests",
dependencies: ["DataDeflator"]),
]
)
module.modulemap content:
module DataDeflate [system] {
header "DataDeflate.h"
link "DataDeflate"
export *
}
DataDeflate.h content:
#ifndef DataDeflate_h
#define DataDeflate_h
#import "NSData+Deflate.h"
#endif /* DataDeflate_h */
Now, using these settings (module.modulemap and targets in Package.swift), Swift code has access to Objective-C NSData category.
But only for declaration (NSData+Deflate.h), and not to implementation (NSData+Deflate.m)!
When I add Objective-C method calling, it builds successfully, without any warnings. But unrecognized selector sent to instance error appears when unit tests runs.
What did I forget? How can I connect both declaration and implementation from Objective-C to Swift?

You don't need to use systemLibrary target with a modulemap file for your own ObjC source files because usually it's used as wrapper to available C system libraries such as sqlite3, curl etc. and the modulemap file can be created for your target automatically by XCode.
So you can use simple target:
...
targets: [
...
.target(name: "DataDeflate", path: "Sources/ObjC/DataDeflate"),
]
...
Next you should organise your files inside DataDeflate folder next way:
- Package.swift
+ Sources
+ ObjC
+ DataDeflate
+ include
- DataDeflate.h
- NSData+Deflate.h
- NSData+Deflate.m
...
Where include subfolder must contain <YourTargetName>.h (DataDeflate.h) file and all other public headers to include.

Related

Xcode, clang, c++ stdlib: Swift Package using a system library, build fails with 'stdexcept' file not found

I am writing an executable Swift package where I need to use a system library (written in C++).
AFAIK I have the package.swift, module.modulemap and umbrella header file written correctly.
When I add an import for the library in my main.swift file I get an error 'stdexcept' file not found. The error comes from an #include <stdexcept> in one of the system library's public header files.
Currently running:
Xcode v13.2.1
macOS v12.2.1 (Monterey)
I think the problem is related to Xcode's Command Line Tools but how do I fix it?
Package.swift
// swift-tools-version:5.5
import PackageDescription
let package = Package(
name: "GeodesicApp",
platforms: [.macOS(.v11)],
dependencies: [
],
targets: [
.systemLibrary(name: "geographiclib",
pkgConfig: "geographiclib",
providers: [
.brew(["geographiclib"])
]
),
.executableTarget(
name: "GeodesicApp",
dependencies: ["geographiclib"])
]
)
module.modulemap
module geographiclib {
umbrella header "geographiclib.h"
export *
link "geographiclib"
}
Umbrella header (geographiclib.h)
#include <GeographicLib/Config.h>
#include <GeographicLib/Geodesic.hpp>
main.swift
import geographiclib // error: 'stdexcept' file not found
... //
Error.
Answering my own question.
I was only able to get a working solution by creating a C wrapper package around the system library; that C wrapper package, in turn, is then wrapped with another Swift wrapper to expose 'Swifty-style' code - I was not able to get a single package that included all the required parts.
My working solution is as follows...
Package: CGeographicLib
Folder structure for the the system library's C wrapper is:
.
├── Package.swift
├── README.md
└── Sources
├── CGeographicLib
│   ├── CGeodesic.cpp
│   └── include
│   └── CGeodesic.h
└── geographiclib
├── geographiclib.h
└── module.modulemap
Updated Package.swift:
import PackageDescription
let package = Package(
name: "CGeographicLib",
platforms: [.macOS(.v11)],
products: [
.library(name: "CGeographicLib", targets: ["CGeographicLib"])
],
targets: [
.systemLibrary(name: "geographiclib",
pkgConfig: "geographiclib",
providers: [
.brew(["geographiclib"])
]),
.target(name: "CGeographicLib", dependencies: ["geographiclib"])
],
cxxLanguageStandard: .cxx20
)
I added platforms: [.macOS(.v11)] as the latest version of the GeographicLib system library only supports macOS v11 or later.
The system library that I am using has some C++11 extensions, I added the language standard .cxx20, but this could equally be .cxx11 too and it should still work for the system library I am using.
Updated module.modulemap:
module geographiclib [system] {
umbrella header "geographiclib.h"
link "geographiclib"
export *
}
Umbrella header, geographiclib.h is unchanged.
For the new C wrapper elements:
CGeodesic.h:
#ifdef __cplusplus
extern "C" {
#endif
double geoLibInverse(double lat1, double lon1, double lat2, double lon2);
#ifdef __cplusplus
}
#endif
CGeodesic.cpp:
#include "include/CGeodesic.h"
#include "../../geographiclib/geographiclib.h"
double geoLibInverse(double lat1, double lon1, double lat2, double lon2) {
using namespace std;
using namespace GeographicLib;
Geodesic geod(Constants::WGS84_a(), Constants::WGS84_f());
double s12;
geod.Inverse(lat1, lon1, lat2, lon2, s12);
return s12;
}
Package: SwiftyGeographicLib
Folder structure for the Swift package that uses the C wrapper package is:
.
├── Package.swift
├── README.md
├── Sources
│   └── SwiftyGeographicLib
│   └── Geodesic.swift
└── Tests
└── SwiftyGeographicLibTests
└── SwiftyGeographicLibTests.swift
Package.swift:
import PackageDescription
let package = Package(
name: "SwiftyGeographicLib",
platforms: [.macOS(.v11)],
products: [
.library(
name: "SwiftyGeographicLib",
targets: ["SwiftyGeographicLib"]),
],
dependencies: [
.package(name: "CGeographicLib",
url: "/Users/kieran/codeProjects/z.TestProjects/SPM/CGeographicLib",
branch: "master")
],
targets: [
.target(
name: "SwiftyGeographicLib",
dependencies: ["CGeographicLib"]),
.testTarget(
name: "SwiftyGeographicLibTests",
dependencies: ["SwiftyGeographicLib"]),
]
)
The package dependency in this example is pointing to a local package - I could equally have uploaded and created a version tag on GitHub.
Geodesic.swift:
import Foundation
import CGeographicLib
public func geodesicInverse(lat1: Double, lon1: Double, lat2: Double, lon2: Double) -> Double {
return geoLibInverse(lat1, lon1, lat2, lon2)
}

Including resources in swift package manager manifest

I am porting an objective c cocoa pod over to swift package manager. It has a bundle of fonts in the package. In the original pod spec, there was a link of code that created the bundle.
s.resource_bundles = {
'mathFonts' => [ 'fonts/*.otf', 'fonts/*.plist' ]
}
In the swift package, I created a static bundle, called mathFonts.bundle and added it to the package as shown below.
in the manifest file, I tried to copy the resources as shown below:
import PackageDescription
let package = Package(
name: "iosMath",
platforms: [
.iOS(.v8)
],
products: [
.library(
name: "iosMath",
targets: ["iosMath"]),
],
dependencies: [
],
targets: [
.target(
name: "iosMath",
resources: [.process("mathFonts.bundle")]), // **<- this is where I am adding the resources**
.testTarget(
name: "iosMathTests",
dependencies: ["iosMath"],
cSettings: [
.headerSearchPath("iosMath")// 5
]),
]
)
I'm getting a runtime crash here:
return [NSBundle bundleWithURL:[[NSBundle bundleForClass:[self class]] URLForResource:#"mathFonts" withExtension:#"bundle"]];
When I manually copy the bundle into the directory of the project that I am importing this package into, it works just fine. How do I include this bundle so that it is made accessible when the package is installed?

Swift Package Manager with resources compile errors

I am trying to use resources inside my Package.swift file:
// swift-tools-version:5.3
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "MyPackage",
products: [
.library(
name: "MyPackage",
targets: ["MyPackage"])
],
targets: [
.target(
name: "MyPackage",
resources: [
.process("Resources/one.xml"),
.process("Resources/two.json"),
.process("Resources/three.json"),
]
)
.testTarget(
name: "MyPackageTests",
dependencies: ["MyPackage"]
)
]
)
When I import and compile the package in another project, I am getting lots of errors, such as:
Cannot infer contextual base in reference to member 'target'
or:
Reference to member 'process' cannot be resolved without a contextual type
The files are located in my package project in Sources -> MyPackage -> Resources
I also tried .copy("Resources/one.xml"), etc
What am I missing?
You missed a , after the target close parentheses:
.target(
name: "BioSwift",
resources: [
.process("Resources/unimod.xml"),
.process("Resources/aminoacids.json"),
.process("Resources/elements.json"),
.process("Resources/enzymes.json"),
.process("Resources/functionalgroups.json"),
.process("Resources/hydropathy.json")
]
), // Here is the missed `,`
Also, you don't need to add files one by one! Instead, you can add a directory:
.process("Resources")

Strange Dependency Issue With Swift Package Manager

UPDATE: I gave it a couple of days, with no answers, so I will be starting to modify the repos, in an effort to fix the problem (I'll get it, eventually).
I am absolutely sure that "I'm not holding it right," but there's no way for me to diagnose the issue.
I have also made a similar post on the Swift Forums.
I have this package, which is consumed by this package, which is, in turn, consumed by this app. The first package is also consumed by this package, which is also consumed by the app. That chain works fine, but is also a great deal simpler.
The issue is that I get an error inside the RVS_BlueThoth package during the BlueVanClef build.
The error is consistent with the RVS_Generic_Swift_Toolbox package not building in the RVS_BlueThoth package build (the module is not available), but I can't figure out why. There is nothing but a blank serialized diagnostics file for one of the files that consumes the RVS_Generic_Swift_Toolbox module, and no other errors, other than the file isn't there.
If I build the RVS_BlueThoth package independently, I have no issues, but including it in the BlueVanClef app consistently reports this error.
Like I said, I am sure that the problem is mine. I just can't figure out how to get to it.
Thanks for any help!
(For example, is there any diagnostic utility available for SPM?)
Here's a diagram of the dependencies:
Note the dotted line between RVS_BlueThoth and RVS_Persistent_Prefs. That's because the dependency is only for test harnesses, and is not used by Blue Van Clef.
Here are the various Package.swift Files:
RVS_Generic_Swift_Toolbox:
// swift-tools-version:5.2
import PackageDescription
let package = Package(
name: "RVS_Generic_Swift_Toolbox",
platforms: [
.iOS(.v11),
.tvOS(.v11),
.macOS(.v10_14),
.watchOS(.v5)
],
products: [
.library(
name: "RVS-Generic-Swift-Toolbox",
type: .dynamic,
targets: ["RVS_Generic_Swift_Toolbox"])
],
targets: [
.target(
name: "RVS_Generic_Swift_Toolbox",
path: "./src")
]
)
RVS_Persistent_Prefs (This one works):
// swift-tools-version:5.2
import PackageDescription
let package = Package(
name: "RVS_Persistent_Prefs",
platforms: [
.iOS(.v11),
.tvOS(.v11),
.macOS(.v10_14),
.watchOS(.v5)
],
products: [
.library(
name: "RVS-Persistent-Prefs",
type: .dynamic,
targets: ["RVS_Persistent_Prefs"])
],
dependencies: [
.package(
url: "git#github.com:RiftValleySoftware/RVS_Generic_Swift_Toolbox.git",
from: "1.2.1"
)
],
targets: [
.target(
name: "RVS_Persistent_Prefs",
path: "./src")
]
)
RVS_BlueThoth (This one does not work):
// swift-tools-version:5.2
import PackageDescription
let package = Package(
name: "RVS_BlueThoth",
platforms: [
.iOS(.v11),
.tvOS(.v11),
.macOS(.v10_14),
.watchOS(.v5)
],
products: [
.library(
name: "RVS-BlueThoth",
type: .dynamic,
targets: ["RVS_BlueThoth"]
)
],
dependencies: [
.package(
url: "git#github.com:RiftValleySoftware/RVS_Generic_Swift_Toolbox.git",
from: "1.2.1"
),
.package(
url: "git#github.com:RiftValleySoftware/RVS_PersistentPrefs.git",
from: "1.1.1"
)
],
targets: [
.target(
name: "RVS_BlueThoth",
path: "./src/Source"
)
]
)
OK. Solved. Finally.
I was, indeed, "holding it wrong," but it was not easy to tell.
The best way to debug was to cd to the package directory, and do a swift build. That helped me to see the errors as they happened (They were not reflected in the Xcode logs).
The issue was that I needed to do the dependency in TWO places. I had it only in one. Now that I look back on it, it was a fairly natural structure, but was complicated by the fact that I needed to rename the product of the dependency to use dashes, instead of underscores.
This was fixed by changing the RVS_BlueThoth Package.swift file to like so:
// swift-tools-version:5.2
import PackageDescription
let package = Package(
name: "RVS_BlueThoth",
platforms: [
.iOS(.v11),
.tvOS(.v11),
.macOS(.v10_14),
.watchOS(.v5)
],
products: [
.library(name: "RVS-BlueThoth", type: .dynamic, targets: ["RVS_BlueThoth"])
],
dependencies: [
.package(name: "RVS_Generic_Swift_Toolbox", url: "git#github.com:RiftValleySoftware/RVS_Generic_Swift_Toolbox.git", from: "1.2.1")
],
targets: [
.target(
name: "RVS_BlueThoth",
dependencies: [.product(name: "RVS-Generic-Swift-Toolbox", package: "RVS_Generic_Swift_Toolbox")],
path: "./src/Source"
)
]
)
The money shot was this line:
dependencies: [.product(name: "RVS-Generic-Swift-Toolbox", package: "RVS_Generic_Swift_Toolbox")]
Note that the name argument is "RVS-Generic-Swift-Toolbox", NOT "RVS_Generic_Swift_Toolbox", but the package name had the underscores (I probably could do without the name argument in the global dependency list).
That is because I had to swap underscores for dashes in order to get the app to be accepted by the App Store.
I was also able to simplify the dependency chain a bit, because some dependencies were only required for test harnesses.
UPDATE: I wanted to mention that the thing that made it for me, was the error response from the swift build command. It was awesome. It actually had the code that I needed to use. It was that response that showed me that I need to use dashes, mixed with underscores. That was not something that I was understanding.

Qbs: Can Module install files?

I want to have a module which will export all needed dependencies like include path, library path and will install needed runtime libraries.
Module {
Depends { name: "cpp" }
property path libLocation: ""
cpp.dynamicLibraries: [
"mylib"
]
cpp.staticLibraries: [
"mylib"
]
cpp.includePaths: [
libLocation + "include/",
]
cpp.libraryPaths: [
libLocation + "lib/",
]
Group {
name: "runtime libraries"
qbs.install: true
prefix: 'lib_location/'
files: ["*.dll"]
}
}
Everything works, but files are not installed. Is it possible to do that?
Update 1:
Files are correctly installed:
if full or relative paths are specified directly(as literals)
by using Project's properties.
Working solution:
Module {
...
Group {
name: "runtime libraries"
prefix: "D:/Projects/MyProject/Dependencies/SDL2pp/mingw/bin/" // works!
//prefix: project.dependenciesPath + "SDL2pp/mingw/bin/" // also works!
files: "*.dll"
qbs.install: true
}
}
But when I'm trying to use Module's property it says: "Reference Error: Can't find variable: ..."
Module {
...
property bool installDlls: true
property string libPath: ""
Group {
name: "runtime libraries"
prefix: libPath // Can't find variable
files: "*.dll"
qbs.install: installDlls // Can't find variable
}
}
Also, It is not work if FileInfo module is used for building a path. Outside the Group path was corectly resolved.
import qbs
import qbs.FileInfo
Module {
...
Group {
name: "runtime libraries"
prefix: FileInfo.joinPaths(project.dependenciesPaths, './SDL2pp/mingw/bin/') // silently not works
files: "*.dll"
qbs.install: true
}
}
Conclusion
I've found 2 solutuins of it:
hadrcoded path as a literal. Unportable solution
using Project's property. Portable, but depends on Project item.
I don't know why Module's properties can't be used inside a Group. Are there some limitations or it's a bug?
Late but found this post trying to do the same, maybe it helps other people.
Found out that using a Module's property inside a Group can be done by giving the Module an id and referencing the property using the id like this
Module {
id: mymodule
...
property bool installDlls: true
property string libPath: ""
Group {
name: "runtime libraries"
prefix: mymodule.libPath
files: "*.dll"
qbs.install: mymodule.installDlls
}
}
I'm using Qbs 1.12.1