3

My C library compiles and gets packaged into an xcframework without a problem, but my Swift project that uses this through a Swift Package then has some errors...

Namely, I'm getting

Token is not a valid binary operator in a preprocessor subexpression

This error shows up, for example, for the or in #if defined(__ANDROID__) or defined(__iOS__).

This header file has #include <iso646.h> (which defines or), and I tried #include "iso646.h" for if SWIFT_PACKAGE, but that didn't help. I also tried to hard-code the contents of this system header into my header, without luck.

Why is my Swift project choking on these?

Edit:

I tried a minimal reproducible example, and I get the same error message.

Here's the code:

Minimis.c:

#include "stdio.h"
void f(void) {
  printf("f called from within xcframework");
}

Minimis.h:

#import <Foundation/Foundation.h>

//! Project version number for Minimis.
FOUNDATION_EXPORT double MinimisVersionNumber;

//! Project version string for Minimis.
FOUNDATION_EXPORT const unsigned char MinimisVersionString[];

// In this header, you should import all the public headers of your framework using statements like #import <Minimis/PublicHeader.h>

#include "iso646.h"
#if A and B
    #pragma message ("A and B are defined")
#else
    #warning Neither A nor B are defined
#endif

void f(void);

Creating an xcframework and a Swift Package from that causes the failure on the #if A and B header line when trying to use the Swift Package in a Swift Project.

I tried modifying the module.modulemap file that gets included with my xcframework to include the iso646 header, like:

framework module Minimis {
  umbrella header "Minimis.h"

  export *
  module * { export * }
  
  link "iso646"
}

... but that doesn't resolve the error. I also tried to create a Swift Package Library that just wraps some C standard libraries, but then I didn't see how to include that in my other Swift Package (I'm using a binary target to pull in my xcframework, and the binaryTarget method doesn't take dependencies).

Crag
  • 1,723
  • 17
  • 35

3 Answers3

2

After checking the content of <iso646.h>, I found that and is defined here.

And using some trick I found __ISO646_H was already defined in command line to deliberately prevent someone from using it.

The code is here and only Apple will know why.

https://github.com/apple/swift/blob/c251b3cf56ab107480a8fb1149a788aa101d29e2/lib/ClangImporter/ClangImporter.cpp#L556

Reply from pre-Apple employees https://forums.swift.org/t/including-c-system-headers-through-a-swift-package/66543/7

叶絮雷
  • 151
  • 3
  • 1
    Thank you! This explains why this wasn't working, exactly what I needed to move on. – Crag Aug 04 '23 at 17:51
0

I included an explanation but this is how to solve your problem specifically @Crag

1. Modify the C code:

This is your first option, but it depends on whether you can change the original C code. The issue here is the use of or in #if defined(__ANDROID__) or defined(__iOS__), which Swift doesn't understand.

Replace or with ||, and do the same for and keywords, replacing them with &&. This is because Swift does not understand the and or or keywords from the iso646.h file. So, your line should look like this:

#if defined(__ANDROID__) || defined(__iOS__)

2. Swift and C compatibility:

If you can't or don't want to change the C code, the other option is to make your code compatible with both C and Swift by using separate conditional directives for each language. Here's how:

In your C header files:

#ifdef __SWIFT__
#else
    #include "iso646.h"
    #if defined(__ANDROID__) or defined(__iOS__)
        // C code
    #endif
#endif

And in your Swift code:

#if os(Android) || os(iOS)
    // Swift code
#endif

3. Create a Swift Package with C Libraries:

Now, let's incorporate your C library into a Swift Package. Here are the steps:

Step 1: Add a new package in Xcode (File > New > Swift Package). Name it "YourSwiftPackage".

Step 2: Inside the created package directory, create a new directory called CLib.

Step 3: Move your C library files (both .h and .c) into the CLib directory.

Step 4: Open your Package.swift file and make it look something like this:

// swift-tools-version:5.3

import PackageDescription

let package = Package(
    name: "YourSwiftPackage",
    products: [
        .library(
            name: "YourSwiftPackage",
            targets: ["YourSwiftTarget"]),
    ],
    dependencies: [],
    targets: [
        .target(
            name: "YourSwiftTarget",
            dependencies: ["CLib"]),
        .systemLibrary(
            name: "CLib",
            path: "CLib"
        ),
    ]
)

The systemLibrary target lets you include the C library located in the CLib directory. YourSwiftTarget is your Swift target that depends on it.

That should be it! Now your Swift code should be able to call the C code without preprocessor issues, and your Swift package includes the C library as a dependency.

KFDoom
  • 772
  • 1
  • 6
  • 19
  • because I forgot that I did that! – KFDoom Aug 02 '23 at 22:18
  • Why doesn't Swift use the iso646.h definitions in preprocessor directives? I assumed I was causing this because that would exclude Swift from using lots of C libraries. – Crag Aug 04 '23 at 12:34
  • Swift is a programming language designed and developed by Apple. It is not built on top of C, like Objective-C, and is not a superset of C. This is the primary reason why Swift does not use iso646.h definitions or other standard C libraries in its preprocessor directives. iso646.h is a header file in the C standard library that defines several macros that correspond to various operators in C and C++. While C and C++ are compatible with each other to some extent and can use the same libraries, Swift operates differently. Swift was designed to be safer, more modern, and more expressive than Objc – KFDoom Aug 04 '23 at 22:15
0

You can fix this by using && and || operators instead of and | or. The traditional logical && operator have a higher precedence than the the the more-human readable form or writing and.

So basically preprocessors works with replacing the macros with their values. In this case, and is treated as an identifier cause it can't find a macro named and and leads to the invalid binary operator error.

In your case, something else Kyle pointed out in the forum is that the swift compiler avoids including the iso646 header.

The solution to fix this issue would be to instead use:

#if defined(__ANDROID__) || defined(__iOS__)
// ... rest of the code
Haneen Mahdin
  • 1,000
  • 1
  • 7
  • 13