Linking a C Library and Its Supporting Library in Swift (Linux)

Linking a C library and its supporting library in Swift (linux)

Adding a second link line for libgslcblas will do the trick:

module CGSL [system] {
header "/usr/include/gsl/gsl_rng.h"
link "gsl"
link "gslcblas"
export *
}

You may also need to add link "m", even though I didn't have to do that on my box (Ubuntu 14.04).

I did not find a specific advice on this in Swift documentation and had to make an educated guess, but it worked. Swift on Linux is work in progress and the Package Manager is only available with Swift 3.0 development snapshots, Swift 3.0 being an unstable, actively developed latest version of the language. The very fact that such a common scenario is not well documented should give you an idea of the technology's maturity.

As an alternative to the Package Manager you might want to consider using a bridging header as described in an answer to this question:
Compile C code and expose it to Swift under Linux.

Whichever way you do it, a bigger challenge will be calling the GSL API from Swift because the API uses a lot of non-primitive types. To work around that problem consider writing a C wrapper with a simplified interface that can be easily called from Swift. The wrapper can then be called by using a bridging header or system modules.

Compile C code and expose it to Swift under Linux

If you build a library out of your C code, you can create a system module for it, which can then be imported into Swift, see this answer: Use a C library in Swift on Linux.

Another way to approach this task is to create a bridging header, as suggested by @Philip. Here is an oversimplified example. Let's consider the following C code:

/* In car.h */
int getInt();

/* In car.c */
int getInt() { return 123; }

We will use car.h as the bridging header. The swift source is (in file junk.swift):

print("Hi from swift!")
var i = getInt()
print("And here is an int from C: \(i)!")

First, create an object file, car.o, from car.c:

gcc -c car.c

Now build an executable, junk, as follows:

swiftc -import-objc-header car.h junk.swift car.o -o junk

Running the executable gives:

$ ./junk
Hi from swift!
And here is an int from C: 123!

The -import-objc-header option is hidden. To see it and a bunch of other hidden options, run:

swiftc -help-hidden 

I did this using Swift 3.0 development snapshot for Ubuntu 14.04 from April 12, available here: https://swift.org/builds/development/ubuntu1404/swift-DEVELOPMENT-SNAPSHOT-2016-04-12-a/swift-DEVELOPMENT-SNAPSHOT-2016-04-12-a-ubuntu14.04.tar.gz

Now, if you want to use C++, you will need to create a wrapper, written in a C++ source file and compiled with a C++ compiler, but with functions callable from C by using extern "C". Those functions can then be called from Swift as any C function. See, for example, this answer: Can I mix Swift with C++? Like the Objective - C .mm files

Swift SQLite Linux/Ubuntu

Since Objective-C as used on Apple's platforms is not well supported on Linux, I think a better approach might be to access the SQLite C API from Swift by either using a system module or bridging header. You might also want to wrap the SQLite API in a C library exposing a subset of the API that you need in a simplified form more suitable to be called from Swift. You would then invoke the wrapper by creating a system module for it (no system module is needed in this case for the SQLite API) or by using a bridging header.

The following may help:

Compile C code and expose it to Swift under Linux

Linking a C library and its supporting library in Swift (linux)

Use the sysctlbyname function within a Linux Swift Package

So one can add C, C++ or Objective-C targets to a Swift package so it's possible to import the needed system headers, and then create some wrapper functions, that makes what is needed, accessible to Swift, but this breaks Swift playgrounds app development compatibility, since that support Swift-only targets (a possible workaround is to put the C/C++ target in a separate swift package to use it as a dependecy conditionally just for linux, for more details see the relative swift package documentation).

So adding a C/C++ target could have solved the problem, BUT the issue is that in the Linux kernel version 5.5 and onwards the sysctl functions have been deprecated and even on the older kernels they weren't available on all the cpu architectures Linux supports, and so on a computer running a recent kernel or some particular non-x86 cpu architecture, such Swift package would not have been built successfully.

The current way to access the information that used to be provided by the sysctl functions is to read it directly from the file system inside the /proc/sys directory and it works on all supported cpu architectures, and it's were the sysctl command line utility gets that data.

So only on linux the code have to modified like this, to successfully gather that data on all platforms:


import Foundation

#if os(Linux)
import Glibc //? not sure about where i can find `sysctlbyname` in linux without using C headers
#else
import Darwin.sys.sysctl
#endif

///Generic protocol to allow easy fetching of values out of `sysctlbyname`
public protocol SysctlFetch{
static var namePrefix: String {get}
}

public extension SysctlFetch{

#if !os(Linux)
///Gets a `String` from the `sysctlbyname` function
static func getString(_ valueName: String) -> String?{

var size: size_t = 0

let name = namePrefix + valueName

var res = sysctlbyname(name, nil, &size, nil, 0)

if res != 0 {
return nil
}

var ret = [CChar].init(repeating: 0, count: size + 1)

res = sysctlbyname(name, &ret, &size, nil, 0)

return res == 0 ? String(cString: ret) : nil
}

///Gets an Integer value from the `sysctlbyname` function
static func getInteger<T: FixedWidthInteger>(_ valueName: String) -> T?{
var ret = T()

var size = MemoryLayout.size(ofValue: ret)

let res = sysctlbyname(namePrefix + valueName, &ret, &size, nil, 0)

return res == 0 ? ret : nil
}
#else
///Gets a `String` from `/proc/sys`
static func getString(_ valueName: String) -> String?{

let path = "/proc/sys/" + (namePrefix + valueName).replacingOccurrences(of: ".", with: "/")

var contents = ""

do{
contents = try String(contentsOfFile: path)
}catch let err{
return nil
}

if contents.last == "\n"{
contents.removeLast()
}

return contents
}

///Gets an Integer value from `/proc/sys`
static func getInteger<T: FixedWidthInteger>(_ valueName: String) -> T?{
guard let str = getString(valueName) else { return nil }
return T(str)
}
#endif

///Gets a `Bool` value from the `sysctlbyname` function
static func getBool(_ valueName: String) -> Bool?{
guard let res: Int32 = getInteger(valueName) else{
return nil
}

return res == 1
}

}

So at the end i figured it out on my own, i hope this can be useful to anyone having to do the same thing.

Wrap a C++ library in Swift code, compile as a Cocoa Touch framework

You're probably looking for "Library Search Paths" (LIBRARY_SEARCH_PATHS). You may need to add to "Header Search Paths (HEADER_SEARCH_PATHS) as well, depending on your setup. In this case, you shouldn't have to add anything to the Framework search paths, since you are not dealing with a Framework.

(As an aside, you are likely going to find that you have to wrap your C++ library in Objective-C++ code to expose the functionality to Swift.)

Xcode, clang, c++ stdlib: Swift Package using a system library, build fails with '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...

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

Link to fat Static Library inside Swift Package

I also needed to add the NDI SDK as a Swift package dependency in my app.

I found a couple of approaches that worked:

You can create an XCFramework bundle by extracting a thin arm64 library from the universal library:

lipo "/Library/NDI SDK for Apple/lib/iOS/libndi_ios.a" -thin arm64 -output "$STAGING_DIRECTORY/libndi.a"

Then create an XCFramework bundle:

xcodebuild -create-xcframework -library "$STAGING_DIRECTORY/libndi.a" -headers "/Library/NDI SDK for Apple/include" -output "$STAGING_DIRECTORY/Clibndi.xcframework"

I ended up not using this approach because hosting the XCFramework as a downloadable binary release on GitHub required me to make my repo public (see this issue).

Instead I am using a system library target, in my Package.swift:

    targets: [
.target(
name: "WrapperLibrary",
dependencies: ["Clibndi"],
linkerSettings: [
.linkedFramework("Accelerate"),
.linkedFramework("VideoToolbox"),
.linkedLibrary("c++")
]),
.systemLibrary(name: "Clibndi")
]

Then, I have WrapperLibrary/Sources/Clibndi/module.modulemap that looks like:

module Clibndi {
header "/Library/NDI SDK for Apple/include/Processing.NDI.Lib.h"
link "ndi_ios"
export *
}

Finally, my application target (part of an Xcode project, not a Swift package) depends on WrapperLibrary, and I had to add "/Library/NDI SDK for Apple/lib/iOS" (including the quotation marks) to "Library Search Paths" in the "Build Settings" tab.

As an alternative to modifying the application target build settings, you could add a pkg-config file to a directory in your pkg-config search paths. For example, /usr/local/lib/pkgconfig/libndi_ios.pc:

NDI_SDK_ROOT=/Library/NDI\ SDK\ for\ Apple

Name: NDI SDK for iOS
Description: The NDI SDK for iOS
Version: 5.1.1
Cflags: -I${NDI_SDK_ROOT}/include
Libs: -L${NDI_SDK_ROOT}/lib/iOS -lndi_ios

Then use .systemLibrary(name: "Clibndi", pkgconfig: "libndi_ios") in your package manifest. I found this less convenient for users than just adding the setting to my application target, however.

Ideally you could add the NDI SDK's dependency library and frameworks to the pkg-config file as well (Libs: -L${NDI_SDK_ROOT}/lib/iOS -lndi_ios -lc++ -framework Accelerate -framework VideoToolbox), but it appears there is a bug in Swift's pkg-config parsing of -framework arguments, so I filed a bug: SR-15933.

iOS:Unix:Mac extract info from a static lib regarding supported architecture(s). How?

You may use otool for getting that information.

From otool's manpage

-L Display the names and version numbers of the shared libraries that the object file uses. As well as the shared library ID if the
file is a
shared library.

Example

> otool -L libRaptureXML_universal.a 

Archive : libRaptureXML_universal.a (architecture armv7)
libRaptureXML_universal.a(RXMLElement.o) (architecture armv7):
Archive : libRaptureXML_universal.a (architecture i386)
libRaptureXML_universal.a(RXMLElement.o) (architecture i386):


Related Topics



Leave a reply



Submit