swiftxcodewrapperswift-package-managerxcode-command-line-tools

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:

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


Solution

  • 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)
    }