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:
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
... //
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...
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;
}
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)
}