1

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. XCode Error

Cœur
  • 37,241
  • 25
  • 195
  • 267
KieranC
  • 57
  • 6

1 Answers1

0

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)
}
KieranC
  • 57
  • 6